摘要:经过上一篇教程的学习,我们知道对象将它的状态存在域中。然而,Java中也使用了“变量”这个术语。在这一篇教程中,我们将会讨论它们之间的关系,以及变量命名的规则和惯例,基本数据类型以及它们的默认值和字面量。
一.变量
1.变量的定义
正如上一篇教程《Java基础教程(4)--面向对象概念》中介绍的那样,对象将它的状态存在域中。但是你可能仍然有一些疑问,例如:命名一个域的规则和惯例是什么?除了int还有其他的类型吗?域在声明的时候必须初始化吗?如果域没有显示地初始化会被赋予一个默认值吗?在本文中我们将一一讨论这些问题,但是在开始之前,需要对几个概念进行介绍。在Java中,“域”和“变量”术语都会使用,对于初学者来说这可能有一点困惑,因为它们看起来好像说的是同一个东西。
Java语言定义了以下几种类型的变量:
- 实例变量(非静态域):从技术层面来说,对象将它们各自的状态存在“非静态域”中,也就是没有使用static关键字修饰的域。非静态域也被称为实例变量,因为对于类的每个实例(换句话说,就是每个对象)来说,它们的值都是独立的。例如,每个自行车的当前速度相对于其他自行车来说都是独立的。
- 类变量(静态域):类变量是使用static关键字修饰的域。这告诉编译器无论这个类有多少个实例,这个变量只有一个副本。对于一种特定的自行车来说,它的齿轮数可以被标记为static,因为这个值适用于它的每一个实例。代码static int numGears = 6;将会创建一个静态域。此外,可以使用关键字final来修饰这个域来保证它的值不会改变。
- 局部变量:类似于对象在域中存储其状态,方法通常会将其临时状态存储在局部变量中。声明局部变量的语法与声明域类似(例如,int count = 0;)。没有特殊的关键字将变量标记为局部变量,这完全取决于声明变量的位置——它位于方法的两个大括号之间。因此,局部变量只对声明它们的方法可见,对于类的其他部分来说它们是不可见的。
- 参数:你已经在HelloWorld的main方法和Bicycle类中看到过参数的应用。回想一下main方法的签名——public static void main(String[] args)。这里,args变量是方法的参数。需要记住的是参数属于“变量”而不是“域”。这也适用于其他接受参数的结构(例如构造函数和异常处理),我们将陆续在后面的教程中见到它们。
在理解了这几个概念后,相信你对变量和域的关系有了进一步的认识。变量分为实例变量、类变量、局部变量和参数,而只有实例变量和类变量属于域的概念。你也可能偶尔也会看到“成员”一词,类的域,方法和嵌套类型统称为其成员。
2.命名
变量名是标识符的一种,它满足标识符的规则。标识符用来给程序中需要自定义名称的某个实体命名,例如变量、方法、类、参数等。所有的标识符都需要满足以下的规则或惯例:
- 标识符必须是由字母、数字、下划线(_)、美元符号($)等Java允许作为标识符中一部分的字符组成。可以使用Character.isJavaIdentifierPart()来检测一个字符是否被允许作为Java标识符的一部分。
- 标识符不能以数字或其他不允许作为出现在标识符起始位置的字符开头。可以使用Character.isJavaIdentifierStart()来检测一个字符是否被允许作为Java标识符的第一个字符。
- 标识符不能是关键字、null、true或false。
- 标识符不限制长度。
- 标识符应该尽可能地表达出它的作用或意义以提高程序的可读性。建议使用驼峰法(第一个单词首字母小写,其余单词首字母大写)来为设计标识符。如果是常量(使用final修饰的变量),那么规则稍有变化,将每个字母大写并用下划线(_)分隔每个单词。
上面提到了关键字。关键字是编程语言中事先定义的,有特别意义的单词。下面是Java中的关键字:
其中,const和goto关键字虽然已经不再使用,但是它们还是被保留了下来。true、false和null虽然被很多人误认为是关键字,但实际上它们只是字面量而已。
二.基本数据类型
1.八种基本数据类型
Java是一门静态语言,这意味着所有的变量在使用前必须先进行声明。考虑以下代码:
int gear = 6;
上面的代码声明了一个名为gear的变量,它是整数类型,并且有一个初始值1。变量的类型决定了它所能存储的数据的类型。除int之外,Java编程语言还支持其他七种基本数据类型。Java中的八种原始数据类型是:
- byte:byte数据类型是8位有符号整数。它的最小值为-128(-27),最大值为127(27-1)。
- short:short数据类型是16位有符号整数。它的最小值为-32768(-215),最大值为32767(215-1)。
- int:int数据类型是32位有符号整数。它的最小值为-231,最大值为231-1。
- long:long数据类型是64位有符号整数。它的最小值为-263,最大值为263-1。
- float:float数据类型是单精度32位IEEE754浮点数(如果对浮点数的概念不了解,可以简单地将它理解为小数)。它的有效位数为6~7位,不要使用它去存储对精度要求较高的数据。
- double:double数据类型是双精度64位IEEE754浮点数。它的有效位数为15位。在保存浮点数时,绝大部分情况下都应该使用double类型。
- boolean:boolean数据类型只有两个可能的值:true和false。它一般用来表示条件的真或假。在Java中,boolean数据类型不能与其他数据类型进行相互转换。
- char:char数据类型是单个16位Unicode字符。它的最小值为'\u0000',最大值为'\uffff'。
char类型扩展
要想弄清楚char类型,就必须了解Unicode编码机制。Unicode打破了传统字符编码机制的限制。在Unicode出现之前,已经有许多不同的标准:美国的ASCII、西欧语言中的ISO8859-1、俄罗斯的KOI-8、我国的GB2312等。这样就产生了下面两个问题:一是对于任意给定的编码值,在不同的编码方案下有可能对应不同的字母;二是采用大字符集的语言其编码长度可能不同。例如,有些常用的字符采用单字节编码,而另一些字符则需要两个或更多字节。
设计Unicode编码的目的就是要解决这些问题。在20世纪80年代开始启动设计工作时,人们认为两个字节的代码宽度足以对世界上各种语言的所有字符进行编码,并有足够的空间留给未来扩展。在1991年发布了Unicode 1.0,当时仅占用65536个代码值中不到一半的部分。在设计Java时决定采用16位的字符集,这样会比使用8位字符集的程序设计语言有很大的改进。
十分遗憾,经过一段时间,不可避免的事情发生了。由于增加了大量的汉语、日语和韩语中的文字,Unicode字符超过了65536个,16位的char类型已经不能满足描述所有Unicode字符的需要了。
从JavaSE 5.0开始,码点(code point)是指与一个编码表中的某个字符对应的代码值。在Unicode标准中,码点采用十六进制书写,并加上前缀U+,例如U+0041就是拉丁字母A的码点。Unicode的码点可以分成17个平面。第一个代码级别称为基本多语言平面,码点从U+0000到U+FFFF;其余的16个平面码点从U+10000到U+10FFFF,其中包括一些辅助字符(supplementary character)。
下面来介绍UTF-16。UTF-16是Unicode码的一种编码格式。也就是说,Unicode决定了每个字符所对应的编码的值,而UTF-16是Unicode编码的一种书写格式,与其类似的还有UTF-8和UTF-32。虽然这些格式的表现形式不尽相同,但他们表示的编码是一致的,那就是Unicode编码。UTF-16采用不同长度的编码表示所有Unicode码点。在Unicode中,特定长度的比特序列称为代码单元。例如,UTF-8的一个代码单元的长度为8。UTF-16中16位表示一个代码单元。UTF-16编码的规则如下:
- U+0000到U+D7FF以及U+E000到U+FFFF(基本多语言平面)
这个区间称为基本多语言平面,包含了最常见的字符。每个字符对应的码点使用一个代码单元就可以表示。 - U+D800到U+DFFF(代理区)
因为除基本多语言平面外,其他16个平面的码点无法用2个字节表示,所以Unicode标准规定,基本多语言平面内的U+D800到U+DFFF的码点不对应于任何字符,称为代理区。因此,UTF-16利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。 - U+10000到U+10FFFF(辅助平面)
辅助平面中的码点都大于U+FFFF,无法用16位来表示,因此采用一对连续的代码单元来进行编码。具体步骤如下:
a.码点减去0x10000,得到的结果范围在0x00000到0xFFFFF,使用二进制表示为yyyy yyyy yyxx xxxx xxxx;
b.高10位的值(范围为0x000到0x3FF),加上0xD800,得到的结果范围在0xD800到0xDBFF,称为高位代理,作为第一个代码单元;
c.低10位的值(范围也是0x000到0x3FF),加上0xDC00,得到的结果范围在0xDC00到0xDFFF,称为地位代理,作为第二个代码单元;
d.最终的UTF-16编码用二进制表示就是:1101 10yy yyyy yyyy 1101 11xx xxxx xxxx。
在Java中,char类型描述了UTF-16编码中的一个代码单元。建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。
2.默认值
在声明一个域时,如果不对它赋值,编译器将赋予它一个默认值。下面是这8种基本数据类型的默认值:
局部变量则略有不同,编译器永远不会为未初始化的局部变量分配默认值。如果没有初始化局部变量,请保证在使用它之前为其赋值。访问未初始化的局部变量将导致编译时错误。
3.字面量
你可能已经注意到在初始化基本数据类型的变量时不使用new关键字。基本数据类型是语言中内置的特殊数据类型,它们不是从类创建的对象。字面量(literal)是用于表达源代码中一个固定值的表示方法。如下所示,可以将字面量分配给基本数据类型的变量:
boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;
(1)整型字面量
整型字面量默认为int类型。可以在整数后面加上后缀L或l来表示long类型字面量。可以使用前缀0x来表示十六进制,0来表示八进制,0b来表示二进制:
// The number 26, in decimal
int decVal = 26;
// The number 26, in hexadecimal
int hexVal = 0x1a;
// The number 26, in octal
int octVal = 032;
// The number 26, in binary
int binVal = 0b11010;
(2)浮点型字面量
浮点型字面量默认为double类型(也可以加上D或d,不过一般省略)。可以在浮点数后面加上后缀F或f来表示float类型字面量。
可以使用科学计数法来表示浮点数字面量。例如,1.2345*104可以表示为1.2345E4,1.2345*10-4可以表示为1.2345E-4。E后面的数字表示10的指数,也可以使用小写字母e。也可以使用十六进制表示浮点数。例如,3.875=(11.111)2=(3.e)16=(3e)16*2-4,那么3.875可以表示成0x3.ep0或0x3ep-4(因为e和十六进制的15重复,所以这里使用p表示指数)。注意,尾数采用十六进制,指数采用十进制。指数的基数是2,而不是10。
(3)字符型字面量
char类型的字面量值要用单引号括起来。例如,'B'是编码值为66所对应的字符常量。它与"B"不同,"B"是一个包含字符B的字符串。char类型的值可以表示为十六进制值,但需要加上前缀\u,其范围从\u0000~\uFFFF。例如,\u03C0表示圆周率符号π。
除了转义序列\u外,还有一些用于表示特殊字符的转义序列,如下表:
所有这些转义序列都可以出现在字符字面量或字符串中。例如'\u2018'或"Hello\n"。转义序列\u还可以出现在字符字面量或字符串之外,而其他转义序列就不可以。例如
public static void main(String\u005B\u005D args)
就完全符合语法,\u005B和\u005D是[和]的编码。但要注意的是,转义序列\u会在编译代码前得到处理。例如,"\u0022+\u0022"并不是一个由双引号包围加号组成的字符串。实际上,\u0022会在编译代码之前替换为",这个字符串会变成""+"",也就是一个空串。更隐蔽的,一定要注意注释中的\u,注释
// Unicode \u000A is a new line
会产生一个语法错误,因为\u000A会被替换成换行符,也就是说,上面的注释会变成下面这样:
// Unicode
is a new line
类似地,下面的注释也会产生语法错误:
// Look inside c:\users
因为\u后面并没有跟着一个十六进制数。
(4)在数字字面量中使用下划线
在JavaSE7及之后的版本中,任意个数的下划线(_)可以出现在数字字面量中的任意两个数字之间。这个功能可以提高数字字面量的可读性,类似于使用逗号或空格等标点符号将每三个数字分为一组的形式。
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;