3.3 数据类型
强类型语言
Java是一种强类型语言。这就意味着必须为每一个变量声明一种类型。在Java中,一共有8种基本类型(primitive type),其中有4种整型、2种浮点类型、1种字符类型char(用于表示Unicode编码的代码单元)和1种用于表示真值的boolean类型。
整型
整型用于表示没有小数部分的数值,允许是负数。Java提供了4种整型,具体内容如下:
Java整型
类型 | 存储需求 | 取值范围 |
---|---|---|
int | 4字节 | -2 147 483 648 ~ 2 147 483 647(刚刚超过20亿) |
short | 2字节 | -32 768 ~ 32 767 |
long | 8字节 | -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
byte | 1字节 | -128 ~ 127 |
在通常情况下,int类型最常用。但如果想要表示整个地球的居住人口,就需要使用long类型了。byte和short类型主要用于特定的应用场合,例如,底层的文件处理或者存储空间很宝贵时的大数组。
在Java中,整型的范围与运行Java代码的机器无关。这就解决了软件从一个平台移植到另一个平台,或者在同一个平台中的不同操作系统之间进行移植给程序员带来的诸多问题。与此相反,C和C++程序会针对不同的处理器选择最为高效的整型,这样就会造成一个在32位处理器上运行很好的C程序在16位系统上运行时却发生整数溢出。由于Java程序必须保证在所有机器上都能够得到相同的运行结果,所以各种数据类型的取值范围必须固定。
长整型数值有一个后缀L或l(如4000000000L)。十六进制数值有一个前缀0x或0X(如0xCAFE)。八进制有一个前缀0,例如,010对应十进制中的8。很显然,八进制表示法比较容易混淆,所以建议最好不要使用八进制常数。
从Java7开始,加上前缀0b或0B就可以写二进制数。例如,0b1001就是9。另外,同样是从Java7开始,还可以为数字字面量加下划线,如用1_000_000(或0b1111_0100_0010_0100_0000)表示100万。这些下划线只是为了让人更易读。Java编译器会去除这些下划线。
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println(1_000_000);
System.out.println(0b1111_0100_0010_0100_0000);
}
}
运行结果
1000000
1000000
如果要使用不可能为负的整数值而且确实需要额外的一位(bit),也可以把有符号整数值解释为无符号数,但是要非常仔细。例如,一个byte值b可以不表示范围-128到127,如果你想表示0到255的范围,也可以存储在一个byte中。基于二进制算术运算的性质,只要不溢出,加法、减法和乘法都能正常计算。但对于其他运算,需要调用
Byte.toUnsignedInt(b)
来得到一个0到255的int值,然后处理这个整数值,再把它转换回byte。Integer和Long类都提供了处理无符号除法和求余数的方法。
浮点类型
浮点类型用于表示有小数部分的数值。
两种浮点类型
在Java中有两种浮点类型,具体内容如下表所示:
类型 | 存储需求 | 取值范围 |
---|---|---|
float | 4字节 | 大约 ±3.402 823 47E(有效位数为6 ~ 7位) |
double | 8字节 | 大约 ±1.797 693 134 862 315 70E+308(有效位数为15位) |
double表示这种类型的数值精度是float类型的两倍(有人称之为双精度数值)。在很多情况下,float类型的精度(6 ~ 7位有效数字)并不能满足需求。实际上,只有很少的情况适合使用float类型,例如,需要单精度数的库,或者需要存储大量数据时。
float类型的数值有一个后缀F或f(例如,3.14F)。没有后缀F的浮点数值(如3.14)总是默认为double类型。当然,也可以在浮点数值后面添加后缀D或d(例如,3.14D)。
注释
可以使用十六进制表示浮点数值。例如,0.125=2^(-3)可以表示成0x1.0p-3。在十六进制表示法中,使用p表示指数,而不是e。(e是一个十六进制数位。)注意,尾数采用十六进制,指数采用十进制。指数的基数是2,而不是10。
三个特殊的浮点数值
所有的浮点数值计算都遵循IEEE 754规范。具体来说,下面是用于表示溢出和出错情况的三个特殊的浮点数值:
- 正无穷大
- 负无穷大
- NaN(不是一个数字)
例如,一个正整数除以0的结果为正无穷大。计算0/0或者负数的平方根结果为NaN。
注释
常量Double.POSITIVE_INFINITY、Double.NEGATIVE_INFINITY和Double.NaN(以及相应的Float类型的常量)分别表示这三个特殊的值,但在实际应用中很少遇到。特别要说明的是,不能如下检测一个特定值是否等于Double.NaN:
if(x==Double.NaN) //is never true
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
double x=3.14;
if(x==Double.NaN)
{
System.out.println("黄子涵");
}
}
}
运行结果
所有“非数值”的值都认为是不相同的。不过,可以如下使用Double.isNaN方法来判断:
if(Double.isNaN(x)) //check whether x is "not a number"
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
double x=3.14;
if(Double.isNaN(x))
{
System.out.println("黄子涵");
}
}
}
运行结果
警告
浮点数值不适用于无法接受舍入误差的金融计算。例如,命令
System.out.println(2.0-1.1)
将打印出0.8999999999999999,而不是人们期望的0.9。这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数1/10。这就好像十进制无法精确地表示分数1/3一样。如果在数值计算中不允许有任何舍入误差,就应该使用BigDecimal类。
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println(2.0-1.1);
}
}
运行结果
0.8999999999999999
char类型
char类型原本用于表示单个字符。不过,现在情况已经有所变化。如今,有些Unicode字符可以用一个char值描述,另外一些Unicode字符则需要两个char值。
char类型的字面量值要用单引号括起来。例如:'A'是编码值为65的字符常量。它与"A"不同,"A"是包含一个字符A的字符串。char类型的值可以表示为十六进制值,其范围从\u0000到\uFFFF。例如,\u2122表示商标符号(™),\u03C0表示希腊字母(π)。
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println('A');
System.out.println("A");
}
}
运行结果
A
A
除了转义序列\u之外,还有一些用于表示特殊字符的转义序列,请参看表(特殊字符的转义序列)。所有这些转义序列都可以出现在加引号的字符字面量或字符串中。例如,'\u2122'或'Hello\n'。转义序列\u还可以出现在加引号的字符常量或字符串之外(而其他所有转义序列不可以)。例如:
public static void main(String\u005B\ue05D args)
就完全符合语法规则,\u0058和\u005D分别是[和]的编码。
程序示例
public class HuangZiHanTest
{
public static void main(String\u005B\u005D args)
{
}
}
运行结果
特殊字符的转义序列
转义序列 | 名称 | Unicode值 |
---|---|---|
\b | 退格 | \u0008 |
\t | 制表 | \u0009 |
\n | 换行 | \u000a |
\r | 回车 | \u000d |
\" |
双引号 | \u0022 |
\' |
单引号 | \u0027 |
\\ |
反斜杆 | \u005c |
警告
Unicode转义序列会在解析代码之前得到处理。例如,"\u0022+\u0022"并不是一个由引号(U+0022)包围加号构成的字符串。实际上,\u0022会在解析之前转换为”,这会得到""+"",也就是一个空串。
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println("\u0022+\u0022");
}
}
运行结果
注释中的\u
更隐秘地,一定要当心注释中的\u。注释
// \u000A is a newline
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println("黄子涵"); // \u000A is a newline
}
}
运行结果
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
is cannot be resolved to a type
Syntax error on token "newline", ; expected
at HuangZiHanTest.main(HuangZiHanTest.java:6)
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println("黄子涵"); // \u000A //is a newline
}
}
运行结果
黄子涵
会产生一个语法错误,因为读程序时\u00A0会替换为一个换行符。类似地,下面这个注释
//look inside c:\users
也会产生一个语法错误,因为u后面并没有跟着4个十六进制数。
程序示例
public class HuangZiHanTest
{
public static void main(String[] args)
{
System.out.println("黄子涵"); //look inside c:\users
}
}
运行结果
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Invalid unicode
at HuangZiHanTest.main(HuangZiHanTest.java:6)
Unicode和char类型
要想弄清char类型,就必须了解Unicode编码机制。Unicode打破了传统字符编码机制的限制。
编码标准
在Unicode出现之前,已经有许多种不同的标准:美国的ASCII、西欧语言中的ISO 8859-1、俄罗斯的KOI-8、中国的GB 18030和BIG-5等。这样就产生了下面两个问题:
一个是对于任意给定的代码值,在不同的编码方案下有可能对应不同的字母;二是采用大字符集的语言其编码长度有可能不同。例如,有些常用的字符采用单字节编码,而另一些字符则需要两个或多个字节。
设计Unicode编码的目的
设计Unicode编码的目的就是要解决这些问题。在20世纪80年代开始启动统一工作时,人们认为两个字节的代码宽度足以对世界上各种语言的所有字符进行编码,并有足够的空间留给未来扩展,当时所有人都这么想。在1991年发布了Unicode 1.0,当时仅占用65 536个代码值中不到一半的部分。在设计Java时决定采用16位的Unicode字符集,这比使用8位字符集的其他程序设计语言有了很大的改进。
十分遗憾的是,经过一段时间后,不可避免的事情发生了。Unicode字符超过了65 536个,其主要原因是增加了大量的汉语、日语和韩语中的表意文字。现在,16位的char类型已经不能满足描述所有Unicode字符的需要了。
码点
下面利用一些专用术语来解释Java语言从Java 5开始如何解决这个问题。码点(code point)是指与一个编码表中的某个字符对应的代码值。在Unicode标准中,码点采用十六进制书写,并加上前缀U+,例如U+0041就是拉丁字母A的码点。Unicode的码点可以分成17个代码平面(code plane)。
基本多语言平面
第一个代码平面称为基本多语言平面(basic multilingual plane),包括码点从U+0000到U+FFFF的“经典”Unicode代码;其余的16个平面的码点为从U+10000到U+10FFFF,包括辅助字符(supplementary character)。
代码单元
UTF-16编码采用不同长度的编码表示所有Unicode码点。在基本多语言平面中,每个字符用16位表示,通常称为代码单元(code unit);
替代区域
而辅助字符编码为一对连续的代码单元。采用这种编码对表示的各个值落入基本多语言平面中未用的2048个值范围内,通常称为替代区域(surrogate area)(U+D800 ~ U+DBFF用于第一个代码单元,U+DC00 ~ U+DFFF用于第二个代码单元)。这样设计十分巧妙,我们可以从中迅速知道一个代码单元是一个字符的编码,还是一个辅助字符的第一或第二部分。
在Java中,char类型描述了UTF-16编码中的一个代码单元。
强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。最好将字符串作为抽象数据类型处理。
boolean类型
boolean(布尔)类型有两个值:false和true,用来判定逻辑条件。整型值和布尔值之间不能进行相互转换。