2.3 基本数据类型

Java 支持八种基本数据类型,包括一种布尔类型、一种字符类型、四种整数类型和两种浮点数类型,如表 2-1 所示。四种整数类型和两种浮点数类型的区别在于位数不同,因此能表示的数字范围也不同。

表2-1:Java的基本数据类型

类型 取值 默认值 大小 范围
boolean truefalse false 1 位 NA
char Unicode 字符 \u0000 16 位 \u0000~\uFFFF
byte 有符号的整数 0 8 位 -128~127
short 有符号的整数 0 16 位 -32768~32767
int 有符号的整数 0 32 位 -2147483648~2147483647
long 有符号的整数 0 64 位 -9223372036854775808~9223372036854775807
float IEEE 754 浮点数 0.0 32 位 1.4E-45~3.4028235E+38
double IEEE 754 浮点数 0.0 64 位 4.9E-324~1.7976931348623157E+308

下面几节简要介绍这些基本数据类型。除了基本数据类型之外,Java 还支持称为引用类型的非基本数据类型,2.9 节会介绍。

2.3.1 布尔类型

布尔类型(boolean)表示真值,只有两个可选值,表示两种逻辑状态:开或关,是或否,真或假。Java 使用保留字 truefalse 表示这两个布尔值。

从其他编程语言,尤其是 JavaScript,转到 Java 的程序员要注意,Java 比其他语言对布尔值的要求严格得多:布尔类型既不是整数类型也不是对象类型,而且不能使用不兼容的值代替布尔类型。也就是说,在 Java 中不能使用下面的简写形式:

  1. Object o = new Object();
  2. int i = 1;
  3. if (o) {
  4. while(i) {
  5. // ...
  6. }
  7. }

相反,Java 强制要求编写简洁的代码,明确表明想做什么比较:

  1. if (o != null) {
  2. while(i != 0) {
  3. // ...
  4. }
  5. }

2.3.2 字符类型

字符类型(char)表示 Unicode 字符。Java 使用一种稍微独特的方式表示字符:在传给 javac 的输入中,标识符使用 UTF-8 编码(一种变长编码方式),但在内部使用定长编码(16 位)表示字符。

不过,开发者一般无需担心这个区别。大多数情况下,只需记住,如果想在 Java 程序中使用字符字面量,只需把字符放在单引号中即可:

  1. char c = 'A';

当然,字符字面量可以使用任何一个 Unicode 字符,也可以使用 Unicode 转义序列 \u。而且,Java 还支持一些其他转义序列,用来表示常用的非打印 ASCII 字符,例如换行符以及转义 Java 中某些有特殊意义的标点符号。例如:

  1. char tab = '\t', nul = '\000', aleph = '\u05D0', slash = '\\';

表 2-2 列出了可在字符字面量中使用的转义字符。这些字符也可以在字符串字面量中使用,下一节会介绍。

表2-2:Java转义字符

转义序列 字符值
\b 退格符
\t 水平制表符
\n 换行符
\f 换页符
\r 回车符
\" 双引号
\' 单引号
\ 反斜线
\xxx xxx 编码 的Latin-1 字符,其中 xxx 是八进制数,介于 000 到 377 之间。\x\xx 两种形式也是合法的,例如 \0,但不推荐这么用,因为转义序列只有一个数字,在字符串常量中会导致歧义。这种用法在 \uxxxx 中也不鼓励使用
\uxxxx xxxx 编码的 Unicode 字符,其中 xxxx 是四个十六进制数。Unicode 转义序列可以出现在 Java 程序的任意位置,而不只局限于字符和字符串字面量

字符可以转换成整数类型,也可以从整数类型转换而来。字符类型对应的是 16 位整数类型。字符类型与 byteshortintlong 不同,没有符号。Character 类定义了一些有用的静态方法(static method),用于处理字符,例如 isDigit()isJavaLetter()isLowerCase()toUpperCase()

设计 Java 语言和字符类型时考虑到了 Unicode。Unicode 标准一直在发展,每一个 Java 新版本都会使用最新版 Unicode。Java 7 使用的是 Unicode 6.0,Java 8 使用的是 Unicode 6.2。

最近的几版 Unicode 收录了 16 位编码(或叫码位,codepoint)无法容纳的字符。这些追加的字符是十分少见的汉字象形文字,占用了 21 位,无法使用单个字符表示,必须使用 int 类型表示,或者必须使用“代理对”(surrogate pair)通过两个字符表示。

除非经常使用亚洲语言编写程序,否则很少会遇到这些追加的字符。如果预计要处理无法使用单个字符类型表示的字符,就可以使用 CharacterString 等相关类中提供的方法,使用 int 类型表示码位,然后再处理文本。

字符串字面量

除了字符类型之外,Java 还有一种用于处理字符串的数据类型。不过,String 类型是类,不是基本类型。因为字符串很常用,所以 Java 提供了一种句法,可以直接在程序中插入字符串。字符串字面量是包含在双引号中的任意文本(字符字面量使用单引号)。例如:

  1. "Hello, world"
  2. "'This' is a string!"

字符串字面量中可以包含能在字符字面量中使用的任何一个转义序列(参见表 2-2)。如果想在字符串字面量中插入双引号,可以使用 \" 转义序列。String 是引用类型,本章后面的 2.7.4 节还会深入介绍字符串字面量。第 9 章会更详细地介绍在 Java 中处理 String 对象的一些方式。

2.3.3 整数类型

Java 中的整数类型有 byteshortintlong 四种。如表 2-1 所示,这四种类型之间唯一的区别是位数,即能表示的数字范围有所不同。所有整数类型都表示有符号的数字,Java 没有 C 和 C++ 中的 unsigned 关键字。

这四种类型的字面量形式正如你设想的那样,使用十进制数字,前面还可以加上负号。1 下面是一些合法的整数字面量:

1严格来说,负号是作用在字面量上的运算符,而不是字面量的一部分。

  1. 0
  2. 1
  3. 123
  4. -42000

整数字面量还可以使用十六进制、二进制和八进制形式来表示。以 0x0X 开头的字面量是十六进制数,使用字母 AF(或 af)表示数字的十六进制形式。

整数字面量的二进制形式以 0b 开头,当然,只能使用数字 10。字面量的二进制形式可能很长,所以经常在字面量中使用下划线。在任何数字字面量中,下划线都会被忽略。下划线纯粹是为了提升字面量的可读性。

Java 还支持使用八进制表示整数字面量,以 0 开头,而且不能使用数字 89。这种字面量不常用,除非有必要,否则应该避免使用。下面是一些合法的十六进制、二进制和八进制字面量:

  1. 0xff // 使用十六进制表示的十进制数255
  2. 0377 // 使用八进制表示的十进制数255
  3. 0b0010_1111 // 使用二进制表示的十进制数47
  4. 0xCAFEBABE // 用来识别Java类文件的魔法数

整数字面量是 32 位 int 类型,如果以 Ll 结尾,就表示 64 位 long 类型:

  1. 1234 // int类型
  2. 1234L // long类型
  3. 0xffL // 还是long类型

在 Java 中,如果整数运算超出了指定整数类型的范围,不会上溢或下溢,而是直接回绕。例如:

  1. byte b1 = 127, b2 = 1; // byte类型的最大值是127
  2. byte sum = (byte)(b1 + b2); // 加法运算的结果直接回绕到-128,即byte类型的最小值

如果发生了这种情况,Java 编译器和解释器都不会发出任何形式的警告。进行整数运算时,必须确保使用的类型取值范围能满足计算需要。整数除以零,或者计算除以零后得到的余数,都是非法操作,会抛出 ArithmeticException 异常。

每一种整数类型都有对应的包装类:ByteShortIntegerLong。这些类都定义了 MIN_VALUEMAX_VALUE 常量,表示相应的取值范围。而且还定义了一些有用的静态方法,例如 Byte.parseByte()Integer.parseInt(),作用是把字符串转换成整数。

2.3.4 浮点数类型

在 Java 中,实数使用 floatdouble 数据类型表示。如表 2-1 所示,float 类型是 32 位单精度浮点数,double 是 64 位双精度浮点数。这两种类型都符合 IEEE 754-1985 标准。这个标准规定了浮点数的格式和运算方式。

浮点数可以以字面量形式插入 Java 程序,其格式为一些可选的数字,后跟一个小数点和一些数字。下面是几个示例:

  1. 123.45
  2. 0.0
  3. .01

浮点数字面量还可以使用指数形式(也叫科学记数法)表示,其格式为一个数后面跟着字母 eE 和一个数。第二个数表示 10 的次方,是第一个数的乘数。例如:

  1. 1.2345E02 // 1.2345 * 10^2或123.45
  2. 1e-6 // 1 * 10^-6或0.000001
  3. 6.02e23 // 阿伏加德罗常数:6.02 * 10^23

默认情况下,浮点数是 double 类型。若想在程序中插入 float 类型的字面量,要在数字后面加上 fF

  1. double d = 6.02E23;
  2. float f = 6.02e23f;

浮点数字面量不能使用十六进制、二进制或八进制表示。

浮点数表示的值

由于本质上的限制,大多数实数都不能使用有限的位数进行精确表示。因此,要记住,floatdouble 类型都只能表示实际值的近似值。float 类型是 32 位近似值,至少有 6 个有效数字;double 是 64 位近似值,至少有 15 个有效数字。第 9 章会更详细地说明浮点数表示的值。

除了表示普通的数字之外,floatdouble 类型还能表示四个特殊的值:正无穷大、负无穷大、零和 NaN。如果浮点数运算的结果超出了 floatdouble 能表示的范围上限,得到的是无穷大。如果浮点数的运算结果超出了 floatdouble 能表示的范围下限,得到的是零。

Java 的浮点类型区分正零和负零,具体是哪个值取决于从哪个方向出现的下溢。在实际使用中,正零和负零的表现基本一样。最后一种特殊的浮点数 NaN,是“Not-a-Number”的简称,表示“不是数字”。如果浮点数运算不合法,例如 0.0/0.0,得到的就是 NaN。以下几个例子得到的结果就是这些特殊的值:

  1. double inf = 1.0/0.0; // 无穷大
  2. double neginf = -1.0/0.0; // 负无穷大
  3. double negzero = -1.0/inf; // 负零
  4. double NaN = 0.0/0.0; // NaN

Java 浮点数类型能处理到无穷大的上溢以及到零的下溢,因此浮点数运算从不抛出异常,就算执行非法运算也没事,例如零除以零,或计算负数的平方根。

floatdouble 基本类型都有对应的类,分别为 FloatDouble。这两个类都定义了一些有用的常量:MIN_VALUEMAX_VALUENEGATIVE_INFINITYPOSITIVE_INFINITYNaN

无穷大浮点数的表现和设想的一样,例如,无穷大之间的加减运算得到的还是无穷大。负零的表现几乎和正零一样,而且事实上,相等运算符 == 会告诉你,负零和正零是相等的。区分负零、正零和普通的零有一种方法——把它作为被除数:1.0/0.0 得到的是正无穷大,但是 1.0 除以负零得到的是负无穷大。因为 NaN 不是数字,所以 == 运算符会告诉我们它不等于任何其他数字,甚至包括它自己。若想检查某个 floatdouble 值是否为 NaN,必须使用 Float.isNaN()Double.isNaN() 方法。

2.3.5 基本类型之间的转换

Java 允许整数和浮点数之间相互转换。而且,由于每个字符都对应 Unicode 编码中的一个数字,所以字符与整数和浮点数之间也可以相互转换。其实,在 Java 中,布尔值是唯一一种不能和其他基本类型之间相互转换的基本类型。

类型转换有两种基本方式。把某种类型的值转换成取值范围更广的类型,此时执行的是放大转换(widening conversion)。例如,把 int 字面量赋值给 double 类型的变量和把字符字面量赋值给 int 类型的变量时,Java 会执行放大转换。

另一种方式是缩小转换(narrowing conversion)。把一个值转换成取值范围没那么广的类型时执行的就是缩小转换。缩小转换并不总是安全的,例如把整数 13 转换成 byte 类型是合理的,但把 13 000 转换成 byte 类型就不合理,因为 byte 类型只能介于 -128 和 127 之间。缩小转换可能丢失数据,所以试图缩小转换时 Java 编译器会发出警告,就算转换后的值能落在更窄的取值范围内也会警告:

  1. int i = 13;
  2. byte b = i; // 编译器不允许这么做

不过有个例外,如果整数字面量(int 类型)的值落在 byteshort 类型的取值范围内,就能把这个字面量赋值给 byteshort 类型的变量。

如果需要执行缩小转换,而且确信这么做不会丢失数据或精度,可以使用一种称为“校正”(cast)的语言结构强制 Java 转换。若想执行类型校正,可以在想转换的值前面加一个括号,在括号里写上希望转换成哪种类型。例如:

  1. int i = 13;
  2. byte b = (byte) i; // 把int类型强制转换成byte类型
  3. i = (int) 13.456; // 把double字面量强制转换成int类型,得到的是13

基本类型的校正最常用于把浮点数转换成整数。执行这种转换时,浮点数的小数部分会被直接截掉,即浮点数向零而不是临近的整数舍入。静态方法 Math.round()Math.floor()Math.ceil() 执行的是另一些舍入方式。

大多数情况下,字符类型的表现都和整数类型类似,所以需要 intlong 类型的地方都 可以使用字符。不过,还记得吗,字符类型没有符号,所以即便字符和 short 类型都是 16 位,表现上也有差异:

  1. short s = (short) 0xffff; // 这些比特表示数字-1
  2. char c = '\uffff'; // 还是这些比特,表示一个Unicode字符
  3. int i1 = s; // 把short类型转换成int类型,得到的是-1
  4. int i2 = c; // 把字符转换成int类型,得到的是65535

表 2-3 列出了各种基本类型能转换成何种其他类型,以及转换的方式。其中,字母 N 表示无法转换;字母 Y 表示放大转换,由 Java 自动隐式转换;字母 C 表示缩小转换,需要显式校正。

最后,Y* 表示自动执行的放大转换,但在转换过程中最低有效位可能丢失。把 intlong 类型转换成浮点类型时可能会出现这种情况,详情参见下表。浮点类型的取值范围比整数类型广,所以 intlong 类型都能用 floatdouble 类型来表示。然而,浮点类型是近似值,所以有效数字不一定总与整数类型一样多(浮点数的详细介绍参见第 9 章)。

表2-3:Java基本类型转换

基本类型转换为
booleanbyteshortcharintlongfloatdouble
boolean-NNNNNNN
byteN-YCYYYY
shortNC-CYYYY
charNCC-YYYY
intNCCC-YY*Y
longNCCCC-Y*Y*
floatNCCCCC-Y
doubleNCCCCCC-