时间:2021-07-01 10:21:17 帮助过:19人阅读
这里正式进入 javase 面向对象语言部分 正如 JavaSE 拾遗(0)——JavaSE 主线 中讲到的第三条主线,我打算在总结javase 面向对象语言部分的第一层结构用这条主线——javase 程序的组成元素。选择这条主线,是为了契合面向对象分层表达的思想(继承就是分层表述
这里正式进入 javase 面向对象语言部分
正如 JavaSE 拾遗(0)——JavaSE 主线 中讲到的第三条主线,我打算在总结 javase 面向对象语言部分的第一层结构用这条主线——javase 程序的组成元素。选择这条主线,是为了契合面向对象分层表达的思想(继承就是分层表述的体现,其实分层表达是自然的思考方式,现实中为了方便对事物的表达和记忆,产生了简化对事物的描述的方式,比如用父类代表子类的描述,这种方式其实就是分层表达法,并且分层表达和对比表达通常结合使用的,和组合式表述比较分层表述增加了父类子类的耦合性),但这个不是从具体到抽象的分层表达,而是组合的表述中的分层表述。
站在静态角度看,javase 程序的组成元素主要包括4个层次的内容,包、类、函数、字段、变量。最上层的组成元素是 javase 应用程序系统的话,javase 应用程序系统直接下一层组成元素是包,一个 java 应用程序系统由1个或者多个包构成。包是由一个或者多个类构成,在源码是各个 java 类,在字节码是各个 *.class 文件(ps:接口、枚举、注解我也算在类里面),在jvm中就是各个 Class 类的实例;类的组成元素有 字段(ps:包含 final 修饰的变量)、函数、类(成员类);函数的组成元素有 局部变量、类(局部类)、语句;变量不能在分(ps:变量是最小的数据单元,函数是最小的行为单元),类是最小的封闭单元。
站在动态角度看,在程序执行的时候变量分为引用和基本类型变量,而类的作用要实例化为对象才能体现出来,所以要多一个基本组成元素——对象。
如果从 OOD 设计来看,基本类型变量和引用类型变量都可以当做同样的元素来使用,就是其存储数据的作用,在实现的时候必须有基本类型变量形式的存在,否则“引用类型变量——指向引用”会无限递归下去“;在写代码操作变量的时候,这两种变量也会有区别,基本类型变量提供了对数据的直接描述和操作,引用类型变量还提供了对封装的数据操作的方法(通过 dot 运算符实现,引用类型的直接操作只支持赋值和获取引用),设计的时候我把基本类型变量看做引用类型变量的子集(ps:所以我认为是不是未来的高级程序设计语言会没有基本类型变量,直接把这个封装起来嘛,一个东西干嘛搞两套内容,有木有!)。
从JVM 角度上看 jvm 支持8种基本数据类型,和 reference 类型。
从 JVM 角度来看,一个应用程序主要由三部分和JVM配合完成功能,一个是 stack 中的局部变量和操作数,一个是 heap 中的对象,再有一个就是 方法区中的 Class 字节码实例。在做 OOD 的时候,javase 已有的类库也可以算作组成元素,设计的时候一般先从抽象出发,所以这些具体的内容可以先不考虑,先抽象后具体(先接口后实现),先整体后局部(先上层后底层)。
下面具体讲解 javase 面向对象语法规则,先从最底层元素"变量"说起,这样才符合我们的组合思维方式。
这里没有说与 字段 相关的东西,也没有说 final 和 volaitile 关键字,主要就是局部变量相关的东西。
定义:被 java 语言赋予特殊含义的单词,是 java 语言中的基本元素,可以用他们来定义或者修饰java的构建单元,比如变量、语句、函数、类等,直接由 javac 或者 jvm 提供实现的支持,比如基本数据类型 int short long char boolean ,定义引用数据类型 class interface enum @interface,语句 for while if switch ,访问修饰符 private protected public static final abstract volatile 等单词都是关键字。
我们所写的程序对 javac 来说都是字符串的文本文档,由 javac 对其进行符号解析和词法句法分析,解析之后就可以分为关键字、常量的字面值、标志符的字面值、运算符,其中常量的字面值又可以分各种数据类型,整型、字符型、浮点型、boolean型、引用类型 null、除此之外还有各种符号引用的字面值,比如类名、变量名、函数名等。
特点:java 语言中所有的关键字都是小写
JAVA语言关键字(Keywords):
abstract、assert、boolean、break、byte、case、catch、char、class、const、continue、default、do、double、else、extends、final、finally、float、for、goto、implements、import、instanceof、int、interface、long、native、new、package、private、protected、public、return、short、static、strictfp、super、switch、synchonized、this、throw、throws、transient、try、void、volatile、while
备注:
1、为了与其他编程语言保持一致性,JAVA 保留了const 与 goto,但是并未实现, const 请用“public static final”代替,goto 不可以使用。
2、请注意区分 final 与 finally。final 作为修饰符,用于变量时表示该变量的值不可被修改,用于类和方法时表示该类或者方法不可被继承。finally 与 try 配合使用,用于抛出新异常前或捕捉异常后必须执行的语句,只有 System.exit 可以终止 finally 语句块的执行。
3、请注意区分 throw 与 throws。throw 在方法体内使用,把异常向上传递给调用该方法的方法。throws 用在方法签名行内,表示该方法抛出异常。 4、native 只用于方法修饰符,指出该方法是用与平台相关的语言(例如:C语言) 编写的。 strictfp 用做方法或者类的修饰符,指出该方法或者类中所有表达式中浮点数将严格遵守的 fp 限制规则。从不用于修饰变量。若底层平台能支持更高精度,则 strictfp 将失效。synchonized 只用于方法修饰符,指出该方法只能同时被一个线程访问。transient 可用于成员变量修饰符,防止字段被永久串行化,当串行化对象时,总是跳过用 transient 修饰的字段。
定义:在程序中自定义的一些名称,起标识的作用,它和关键字一起定义了 java 程序的构建单元,并且这些标识符就成了这些构建单元的字面值,组成这些标识符的字符串就成了这些构建单元的符号索引,反射就为我们提供了根据符号索引解析 java 构建单元的功能。在 jvm 中主要以符号索引的形式存在于类常量池中,这些符号索引可以在 jvm 执行程序的时候解析,使得 JVM 很重要的一个功能就是 resolution 符号引用。
Java 中规定标识符只能用 字母、数字、下划线、$ 符号来组成,并且不能以数字开头,其中$符号经常用在 内部类、动态生成的代理类 这种地方。
java 中标识符命名规范:
包名都小写 xxxxyyyy
类名和接口名首字母都大写 XxxxYyyy
变量名和函数名首个单词首字母小写,其他单词首字母大写 xxxxYyyy
常量(这里是指 final 修饰的变量)名字母都大写,每个单词用下划线连接 XXXX_YYYY
数据,信息是以数据的形式存在于 java 语言中,一个计算机程序从计算机角度来看,就是数据和计算机指令的集合(见:JVM指令集作用),从这个角度来看 java 语言对数据的表达方式主要有:常数、变量、常量,根据数据的在计算机中存储方式的不同,我们把数据分为两大类:基本数据类型、引用数据类型
8种基本数据类型
又根据数据在实际生活中的意义的不同,把8种基本数据类型分为3类
布尔型 boolean
(能做什么)布尔型数据用来表达现实中的逻辑结果真、假,常常在程序里有程序执行的条件的地方使用
(是什么)true、false
字符型 char
(能做什么)字符型数据具有语言意义上的意义,可以用来表示我们语言和生活中的字符,用来交流
(是什么)字符型数据用单引号括起来 char c = 'a'; 在编译完成后的 class 文件中字符数据使用 Unicode 字符集,所以 java 字符数据是 16 bits,Unicode 代码点机制,而在 java 源码中,myeclipse 一般默认设置的是 utf-8 编码,我们的操作系统一般是 gbk 编码。
数值型 Number
(能做什么)数值型数据用来表达数值的,数值和数学上的数一个意思,一个数值除了是一个符号外,还有数学意义上的可度量的意义
整数
类型
- byte 1字节有符号
- short 2字节有符号
- int 4字节有符号
- long 8字节有符号
表示方式 3种,10进制、8进制零开头、16进制零x开头默认整型常数是 int 类型long 类型常量后面加 l 或者 L, long number = 50L
浮点
- float 4字节
- double 8字节
默认浮点型常数是double 类型
因为相同浮点数可能有不同精度的,浮点数只要经过运算都有可能改变其精度,所以程序中一般不能用“==”来做比较,一般用大于小于来比较(关于 java 中浮点数精度 问题)。
从我们生活意义上看,整数是浮点数的子集;从计算机底层上来看,但是整数和浮点数在内存从的保存形式区别很大,他们的操作方法差别也很大,这就是为什么整数可以进行逻辑运算,而浮点数不行,这块参照计算机原理部分,保留这个区别我想是为了方便 c 程序猿。其实 JVM 很多运算指令把 boolean char byte short 类型的运算都转为 int 类型的运算。
引用数据类型
- null、类、对象
- 接口
- 数组
- String
(为什么)引用数据类型和基本数据类型数据,最大区别就是在存储形式上,是否有引用数据的保存,基本数据类型,只有数据内容的保存,引用数据类型,还保存有数据内容的引用
我们把计算机表示的数据分为两部分,一个是数据本身,一个就是数据类型,某种数据可以属于多种数据类型(向上转型),数据类型涉及到数据在计算机中的存储方式和使用方式,引用数据类型和基本数据类型就是从这个角度来分的。但是计算机中所有东西都是数据,数据类型也是数据,所有又专门搞了一种数据类型来描述数据类型这种数据这就是 Type 接口。在 java 里面数据类型这种数据主要可以分为 class interface enum @interface 数组 基本数据类型 6大种,它们都以 Class 这种数据类型存储和使用,class enum @interface 数组 它们都有实例,其实例都是 Ojbect 类型,interface 没有自己类型的实例,但有 class 类型的实例,基本数据类型直接保存数据。
String 类型比较特殊,因为 JVM 中一般是用 c、c++ 写的,它们自身就支持 String 类型数据,而 jvm 基本数据类型中是没有 String 类型的,但是在 JVM 的常量池中,却有 utf-8 类型的结构,用来间接存储字符串,比如类名、函数名、字段名、程序中的字符串常量,间接是因为上述类型不是直接持有 utf-8 结构的索引,而是它们自身对应的结构的索引,它们自身机构中最终会索引到 utf-8 结构上。所以 String 类型的实例通常保存在方法区中的类常量池中,而不是保存在堆中。Class 这种数据类型的实例和 String 也一样,保存在方法区中。
(能做什么)常数(据)是用来给我们向编译器表达数据的方式,人与编译器进行交流时使用的数据形式
根据数据的实际意义常量分为:
要使用字符串来获取某种引用类型,1.使用反射可以实现;2.枚举类型可以实现。
(能做什么)在现实中,事物的属性这种信息在事物的运动发展过程中是变化的,或者是只能在事物发展到某个时刻才能确定这些信息,变量就是用来存储这种信息的对象,并把这种信息保存在内存中,方面我们使用语句来操作这些变量,实现事物运动变化的过程的描述。
(为什么)从底层实现来看,变量是一个在内存中位置确定(是指在程序执行的时候,这个变量要么在 java 栈中,要么在堆中),大小确定的存储单元
(是什么)
和常数据相比较,常量和变量的主要特点是用来存储在程序执行过程中(运行时)才能确定的内容,而不是编译器(编译时)就可以确定的内容。
从上层来看,变量名和人名一个道理,主要是提供对数据操作的方法
变量声明格式: int age;
从实现层面来看,变量的声明提供了变量的大小和存储位置的说明(如果是字段,JVM 通过符号索引来确定它的存储位置,具体见:对象的内存结构),变量的类型,说明了要创建的存储单元的大小,变量的名字,说明了创建的存储单元的存储位置
从抽象层面来看,变量的声明是程序员和计算机之间的通信,是通过计算机提供的接口告诉计算机要创造一个什么样的信息存储单元
(ps:基本类型的变量,是一种最基本 的元对象,其他的对象都是由他们构造的,这里的构造不是继承的关系,是组合的关系。
和动态语言相比,java 这种静态语言在变量声明的时候就指定了变量的大小,动态语言是执行程序的时候根据变量值的类型决定变量的大小,这好比一个人的人名没有类型,但是这个人这个实体对象是有类型的。
和弱类型语言相比,java 这种强类型语言,在使用变量的时候,需要先声明变量的类型。而弱类型语言是编译器根据变量的值自动决定变量的类型,并且变量的值的类型改变时,变量的类型亦自动改变
感觉弱类型和动态语言好像完全一样嘛,只是定义不一样,我靠,被搞晕了!!)
java 里面数据类型转换主要遵循两个原则:同语义转换,语义相同的数据类型之间可以转换,主要是指数值类型之间可以转换;同存储转换,存储方式相同的数据类型之间可以转换,主要是 char 和 整型之间可以转换。引用类型之间,父子类之间可以转换,我觉得这个也应该是同语义原则。
类型转换的时候,涉及到范围问题的,如果 java 编译器能判断没有越界的不会报错,否则会报错。
类型转换检查这种事情是编译器干的事情(这从另一个方面说明使用反射的时候类型检查就不能使用了,这也是为什么使用反射的时候很多对象用 Object 类型),因为 jvm 很多指令不是对所有类型支持,只支持 int float double long 类型,所以 java 编译器本身就要进行类型转换,并且在转换之前进行类型转换安全检查,比如
class HelloWorld { public static void main(String [] args) { short s = 1; System.out.println(s); } }
其中红框中的两行就是 short i = 1; 对应的指令,它们都是对 int 类型数据操作的指令,iconst_1 是从常量池加载一个 int 数据到 jvm 当前函数的 操作数栈, istore_1 是把刚刚入栈的 int 数据弹出操作数栈,并写入到 局部变量区第1索引位置(因为这个 static 函数 args 变量占据了 0 索引位置。),经过 javac 之后局部变量 short s 完全就是 int 类型了。
举个栗子说说 javac 对应类型转换的检测,
short s = 1; 这个表达式里面 1 是默认的整型,那么就是 int 类型了,s 是 short 类型,这样的话,相当于默认给1变为(short)1,这种默认的行为,java 编译器是会检查数据类型转换是否可行的,因为是默认,容易犯错误,而不像显示的强制类型转换,开发者是有意识的使用类型转换,此时 java 编译器对类型转换的要求就没有那么严格。隐式转换的时候,在 java 编译器检查类型类型转换遵循“ java 编译器能判断没有越界的不会报错,否则会报错。”。因为 1 是常数,java 编译器自然可以判定有没有越界。而
short s = s + 1; s + 1 是个变量表达式,必须在程序执行的时候,才知道表达式的值,此时 java 编译器就会报错。short s += 1;这个是个特殊的情况,在这里老毕有一句话解释了,说凡是复合赋值运算符都是带有强制类型转换的,也就是显示的强制类型转换,那么 java 编译器就不会报错了,因为你一旦使用复合运算符,java 编译器认为你是有意识的使用类型转换,为什么java编译器这样设定,我觉得可能是复合赋值运算符本来就是给高手准备的一种简易表达形式,java 编译器把使用者设为高手,自然要限制少一点。
不同长度的带符号的数据之间进行数据转换的方法:
short -> int 把 short 类数据按符号位补齐
int -> short 直接截断高位,所以如果数据超出范围会出现问题