3.1 变量
根据第2章可知,对象的状态存储在字段中:
2.1节已经介绍了字段的概念,但读者仍可能会问:命名字段有什么规则和约定 除了整型,是否还有其他数据类型?字段在声明时是否必须初始化?如果字段没有显式初始化,该字段是否会被赋予一个默认值?本章将会详细解答这些问题。但在此之前,先要明确一些技术差别。Java程序语言同时使用字段和变量。对于新的开发人员而言,这是造成概念混淆的主要原因,因为两者经常看似指代同一个事物。Java程序语言定义了如下变量:
- 实例变量(非静态字段)。就技术而言,对象将个体状态存入非静态字段,也就是不用关键字static声明的字段。非静态字段也称为实例变量,因为对每个类实例(即对象)而言,它们的值都是唯一的。比如,一辆自行车的currentSpeed字段与另一辆自行车的currentSpeed字段无关。
- 类变量(静态字段)。类变量是用static修饰符声明的任意字段,这就告诉编译器该变量只存在一个副本,而不管它被实例化多少次。特定类型的自行车的档数字段定义时要用static来声明,因为该档数会应用于所有的实例。代码static int numGears=6;创建该静态字段。此外,也可以添加关键字final表示该档数永远都不会变。
- 局部变量。与对象将状态存入字段类似,方法也将临时状态存入局部变量。声明局部变量的语法与声明字段类似(例如int count=0;)。局部变量在声明时没有特殊的关键字,完全取决于变量的声明位置,也就是说方法体内有效。因此,局部变量只对声明它的方法可见,类的其他部分不能访问该局部变量。
- 参数。在Bicycle类和“Hello World!”实例程序的main方法中我们已经见到过参数的例子。main方法的声明形式是public static void main(String[] args)。其中,变量args是main方法的参数。最重要的一点是,参数总被归为变量,而不是字段。这同样适用于后续章节将要学习的其他支持参数的构造,如构造器和异常处理器。
即便如此,本章后续内容讨论字段和变量时仍采用下述标准进行区分。如果讨论的是一般的字段(不包括局部变量和参数),就采用术语字段。如果讨论适用于上述四种变量,就采用术语变量。如果上下文要求进行区分,就根据情况采用特定的术语(静态字段、局部变量等)。有时也会使用术语成员(member)。类型的字段、方法和嵌入类型等都是它的成员。
3.1.1 命名
程序语言都有自己的命名规则和约定,Java也不例外,其变量的命名有以下规则和约定:
- 变量名区分大小写。变量名可以是任意合法的标识符,如以字母、美元符号($)或下划线(_)开头的无限长的Unicode字母和数字序列。但是,按约定通常采用字母开头,而不是美元符号或下划线。另外,按约定美元符号从来没有使用过。尽管有些自动生成的变量名会包含美元符号,但命名变量时应该尽量避免。这些同样适用于下划线。虽然以下划线开头命名变量是合法的,但并不鼓励采用该方法。变量命名时不支持空格。
- 确定首个字符之后,后续字符可以是字母、数字、美元符号或下划线。当然也有一些约定或常识。命名变量时最好使用完整的单词,而不是神秘的缩写。这样命名可以提高代码的可读性。大多数情况下,这样做会提高代码的自文档化(self-documenting)程度。比如,字段名cadence、speed、gear比缩写c、s、g更直观。切记,变量名不能是关键字或保留字。
- 如果变量名只包含一个单词,就用小写字母表示。如果包含两个或两个以上单词,后续单词的首字母要大写,如gearRatio和currentGear。如果变量保存的是常量值,如static final int NUM_GEARS=6,那么变量名的每个字母都要大写,而且后续单词用下划线分隔。按约定,其他情况都不用下划线。
3.1.2 基本数据类型
Java是静态类型的程序语言,也就是说,所有变量在使用前必须先声明。声明的内容包括类型和名称,如:
该声明告诉程序存在一个字段gear,其数据为数字类型,初始值为1。变量的数据类型决定它可以包含的值以及该变量上可以执行的运算。除了整型(int)以外,Java程序语言还支持其他7种基本数据类型。基本数据类型由语言预定义并用保留关键字命名。基本数据类型的值之间不共享状态。Java程序语言支持以下8种基本数据类型。
1)byte数据类型表示8位带符号二进制补码整数。其最小值为-128,最大值为127。当大型数组的内存存储出问题时,byte数据类型可用于存储内存。byte类型变量的范围有限,可以作为一种文档规范。因此,它也用于代替int类型以简化代码。
2)short数据类型表示16位带符号二进制补码整数。其最小值为-32?768,最大值为32?767。与byte类型一样,short类型也可以用作大型数组内存存储的应急方案。
3)int数据类型表示32位带符号二进制补码整数。其最小值为-231,最大值为231-1。在Java SE 8及更高版本中,可以使用int数据类型表示无符号32位整数,其最小值为0,最大值为232-1。Interger类也支持无符号32位整数。Interger类中增加了类似于compareUnsigned和divideUnsigned的静态方法以支持无符号整数的算术运算。
4)long数据类型表示64位带符号二进制补码整数。其最小值为-263,最大值为263-1。在Java SE 8及更高版本中,可以使用long数据类型表示无符号64位长整型整数,其最小值为0,最大值为264-1。使用的值超出int数据类型支持的范围时可使用该数据类型。Long类也包含类似于compareUnsigned和devideUnsigned的方法以支持无符号长整型数的算术运算。
5)float数据类型表示单精度32位IEEE 754浮点数。其值的分布范围超出本书的讨论范畴,可参考Java语言规范(Java Language Specification)中的“浮点类型、格式和值”部分。与byte和short类型一样,如果要在浮点型大型数组中存储内存,可以采用该类型(而不用下面的double类型)。该类型不能用于表示精确的值,如货币。要表示精确的值,可以使用java.math.BigDecimal类。第9章将会介绍BigDecimal和其他有用的类。
6)double数据类型表示双精度64位IEEE 754浮点数。其值的分布范围超出本书的讨论范畴,可参考Java语言规范中的“浮点类型、格式和值”部分。通常,该数据类型是小数的默认类型。如前所述,也不能用于表示货币等精确值。
7)boolean数据类型只有两个可能值:true和false。它主要用于标记true或false条件。该数据类型表示1位信息,但其“大小”并未精确定义。
8)char数据类型表示16位Unicode字符。其最小值为'u0000'(或0),最大值为'uffff'(或65 535)。
除了这8种基本数据类型外,Java程序语言还提供java.lang.String类支持字符串。将字符串加上双引号会自动创建一个新的String对象,如String s="this is a string";。String对象是不可变的。也就是说,String对象一旦创建,其值就不能改变。就技术而言,String类不是基本数据类型。但考虑到Java对其提供特殊支持,你可能会认为它也是一种基本数据类型。String类的详情参见第9章。
1.默认值
字段声明时,不一定总要赋初值。编译器会为那些已经声明但未初始化的字段赋予一个默认值。默认值通常为0或null,这取决于数据类型。但是依赖默认值的做法是一种不好的编程习惯。表3-1总结了上述数据类型的默认值。
局部变量的处理方式略有不同,编译器不会为未初始化的局部变量赋初值。如果局部变量在声明时不能初始化,那么使用该局部变量之前必须为其赋值。访问未初始化的局部变量会导致编译时错误。
2.字
大家可能已经注意到,初始化基本类型的变量时不能使用关键字new。基本类型是内嵌在Java语言中的特殊数据类型,它们不需要用类来创建。字(或文字,literal)是用源代码表示的固定值。字不需要计算,直接由代码表示。下面给出为主类型变量指派字的例子:
(1)整型字
整型字如果以L或l结尾,就是long类型的;否则就是int类型的。建议使用大写字母L,因为小写字母l与数字1很难区分。
byte、short、int和long类型的整数值可以由int类型的字创建。超出int值范围的long类型的值,可以由long类型的字创建。整型字可以由下述记数系统表示:
- 十进制。基数为10,数字由0到9的整数组成。这也是我们日常用的记数系统。
- 十六进制。基数为16,数字由0到9的整数和A到F的大写字母组成。
- 二进制。基数为2,组成数字只有0和1。
通常的编程中,十进制系统可能是人们唯一用过的记数系统。如果要用其他数制系统,要注意其语法的正确性,前缀0x表示十六进制数,前缀0b表示二进制数,例子如下。
(2)浮点字
浮点字是以字母F或f结尾的float类型的字,或是以字母D或d结尾的double类型的字。浮点字的表示方法有三种:用E或e表示科学记数标记,用F或f表示32位float类型的字,用D或d表示64位double类型的字(默认类型,通常省略)。
(3)字符字和字符串字
char和String类型的字可以包含任意Unicode(UTF-16)字符。如果编辑器和文件系统支持,可以直接在编码中使用这些字符。如果不支持,可以使用转义字符,如用'u0108'表示?,用"Su00ED Seu00Flor"表示西班牙文Sí Senor。用单引号表示字符字,用双引号表示字符串字。转义序列也可以用在程序中的其他地方(如字段名),而不仅仅是字符字或字符串字。
Java程序语言也支持一些特殊的char和String类型的转义序列:b(退格)、t(跳格)、n(换行)、f(换页)、r(回车)、"(双引号)、'(单引号)和\(反斜杠)。
null是一个特殊的字,它可以用作任意引用类型的值。null可以赋值给任意主类型外的其他变量。除了用于测试存在性外,null值很少有其他用处。因此,它通常用于标记某个对象是否可用。
最后还有一种特殊的类字(class literal),其表示形式为“类名.class”(如String.class)。这类字指表示类型本身的对象。
3.在数字文字中使用下划线
数字文字中的数字之间可以使用任意多个下划线(_)。这样就可以对数字文字进行分组,从而提高代码的可读性。
例如,如果代码中包含很多数字组成的数,就可以用下划线将这些数字分成3个一组,其用法与逗号和空格一样。
下面的例子给出了在数字文字中使用下划线的其他方式:
下划线只能放在数字之间,不能放在下述位置:
- 数的开头或末尾
- 浮点字中与小数点相邻的位置
- 后缀F或L前面
- 要求是数字字符串的位置
下面例子说明数字文字中合法和不合法的下划线放置位置(如黑体所示)。
3.1.3 数组
数组是固定数目的单一数据类型值的容器对象。创建数组时要指定其长度。创建后,其长度就固定了。“Hello World!”实例程序的main方法中已经给出了一个数组实例。本小节详细介绍数组。
数组中的每个项都称作元素(element),每个元素都用数字索引(index)访问。如图3-1所示,索引从0开始。因此第9个元素要通过索引8来访问。
下面的ArrayDemo程序创建了一个整型数组,存入值并将这些值显示到标准输出:
上述例子清晰地介绍了数组的语法。但真正编写程序时,可能会用循环构造(looping construct)来迭代数组中的元素,而不是上例中为每个元素都写一行代码。循环构造(for、while、do-while)的内容将在3.4节中介绍。
1.声明变量引用数组
上述程序用下面两行代码声明数组anArray:
与其他类型的变量声明一样,数组声明也包含两个组件:数组类型和数组名。数组的类型写作type[],其中type是包含的元素的数据类型,方括号表示该变量保存数组。这种写法中没有包含数组大小(这是方括号内为空的原因)。数组名可以是任意名称,只要它满足3.1.1节讨论的命名规则和约定。与其他类型的变量一样,该声明不会创建数组,而只是告诉编译器该变量可用于保存特定类型的数组。类似地,可以声明其他数据类型的数组:
也可以将方括号写在数组名后面:
但是,通常不建议采用这种声明方式。方括号表示数组的类型,应该和类型标记符写在一起。
2.创建、初始化和访问数组
创建数组的一个方法是使用new运算符。下述语句来自ArrayDemo程序,它分配了一个带有10个整型元素内存空间的数组,并将该数组赋值给anArray变量:
采用这种方式时,数组大小由花括号之间用逗号分隔的值的个数决定。
也可以用两个或多个方括号声明多维数组(multidimensional array),如String[][] names。其元素必须用相应的索引值来访问。
Java程序语言中,多维数组是组件也是数组的数组。这一点与C或Fortran语言中的数组不同。因此,每行的长度可以不同,如multiDimArrayDemo程序所示。
最后,可以使用内置的length属性来获取数组的大小。下述代码会将数组大小显示在标准输出上:
3.复制数组
System类有arraycopy方法,可用于数组之间数据的高效复制:
两个Object参数分别指定源数组和目标数组,三个int参数分别指定源数组的起始位置、目标数组的起始位置以及待复制的元素个数。
下述ArrayCopyDemo程序声明char类型的数组,其元素构成单词decaffeinated,并使用System (arraycopy)方法将该数组组件的子序列复制到第2个数组中:
4.?数组操作
数组是编程中重要且有用的概念。Java SE提供了一系列与数组相关的常用操作方法。例如,ArrayCopyDemo例子使用System类的arraycopy()方法,而不是手动将源数组中的元素依次存入目标数组。该过程在后台完成,开发人员只需使用一行代码调用该方法即可。
为方便起见,Java SE在java.util.Arrays类中提供了几个数组操作方法(如复制、排序和查找等常见任务)。例如,可以使用java.util.Arrays类的copyOfRange()方法将前述ArrayCopyDemo方法修改为如下的ArrayCopyOfDemo方法。两者的区别在于,使用copyOfRange()方法时,调用方法之前不需要创建目标数组,该方法会返回目标数组。
显然,该例子包含的代码更少,但程序的输出仍然一样,也是caffein。
java.util.Arrays类也提供了其他一些有用的操作方法,例如:
- binarySearch()——在数组中搜索特定值,并返回其位置索引。
- equals()——对两个数组进行比较,并确定两者是否相等。
- fill()——在数组的每个索引位置上填上指定值。
- 升序排序方法——Java SE 8引入两种方法,一种是顺序方法sort(),另一种是并发方法parallelSort()。多处理器系统中大数组的并发排序比顺序排序要快。
3.1.4 小结
Java程序语言同时采用术语字段和变量。实例变量(非静态字段)对每个类的实例都是唯一的。类变量(静态字段)是用static修饰符声明的字段。不管类有多少个实例,都只有一个类变量。局部变量表示方法内的临时状态。参数是为方法提供附加信息的变量。局部变量和参数通常归类为变量,而非字段。命名字段或变量时,应该(或必须)遵守一些命名规则和约定。
八种基本数据类型为byte、short、int、long、float、double、boolean和char。java.lang.String表示字符串。编译器会为这些类型的字段指派一个默认值,但不会为局部变量指派默认值。字(文字)是固定值的源代码表示。数组是固定数目的单一数据类型值的容器对象。创建数组时要指定其长度。创建后,数组长度就固定了。
3.1.5 问题和练习:变量
问题
- 实例化变量又叫作 。
- 类变量又叫作 。
- 局部变量存储临时状态,它在 内声明。
- 方法体内声明的变量叫 。
- Java程序语言支持哪八种基本数据类型?
- 字符串由类 表示。
7. 是固定数目的单一数据类型值的容器对象。
练习
- 创建一个小程序,在其中定义一些字段。试着创建一些非法的字段名,看看编译器会报哪种类型的错误。参考命名规则和约定。
- 在上题创建的程序中,试着不初始化一些字段并输出它们的值。试着不初始化局部变量看看编译器会报什么错。熟悉常见的编译器错误有助于识别程序bug。
答案
相关答案参考
http://docs.oracle.com/javase/tutorial/java/nutsandbolts/QandE/answers_variables.html。