3.6.2 直接量的赋值

通常总是把一个直接量赋值给对应类型的变量,例如下面代码都是合法的

1
2
3
4
5
6
int a =5:
char c='a';
boolean b= true;
float f= 5.12f
double d=4.12;
String str="HelloWorld";

除此之外,Java还支持数值之间的自动类型转换,因此允许把一个数值直接量直接赋给另一种类型的变量,这种赋值必须是系统所支持的自动类型转换,例如把int类型的直接量赋给一个long类型的变量。Java所支持的数值之间的自动类型转换图如图3.10所示,箭头左边类型的直接量可以直接赋给箭头右边类型的变量;
这里有一张图片
如果需要把图3.10中箭头右边类型的直接量赋给箭头左边类型的变量,则需要强制类型转换。
String类型的直接量不能赋给其他类型的变量,
null类型的直接量可以直接赋给任何引用类型的变量,包括String类型。
boolean类型的直接量只能赋给boolean类型的变量,不能赋给其他任何类型的变量。

常量池

常量池(constant Pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括关于类、方法、接口中的常量,也包括字符串直接量。
当程序第一次使用某个字符串直接量时,Java会使用常量池(constant Pool)来缓存该字符串直接量,如果程序后面的部分需要用到该字符串直接量时,Java会直接使用常量池(constant Pool)中的字符串直接量。
由于String类是一个典型的不可变类,因此String对象创建出来就不可能被改变,因此无须担心共享String对象会导致混乱。

看如下程序:

1
2
3
4
5
String s0="hello";
String s1 ="hello";
String s2 ="he"+"llo";
System.out.println( s0 == s1 );
System.out.println( s0 == s2 );

运行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
jshell> /list

1 : String s0="hello";
2 : String s1 ="hello";
3 : String s2 ="he"+"llo";
4 : System.out.println( s0 == s1 );
5 : System.out.println( s0 == s2 );

jshell> /4
System.out.println( s0 == s1 );
true

jshell> /5
System.out.println( s0 == s2 );
true

Java会确保每个字符串常量只有一个,不会产生多个副本。例子中的s0s1中的”hello“都是字符串常量,它们在编译期就被确定了,所以s0==s1返回true;

字符串常量的连接也是字符串常量

而”he“和”llo“也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它本身也是字符串常量,s2同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”hello“的引用。因此,程序输出s0==s1返回true,s1==s2也返回true

3.6 直接量

什么是直接量

直接量是指在程序中通过源代码直接给出的值,例如在int a=5;这行代码中,为变量a所分配的初始值5就是一个直接量。

3.6.1 直接量的类型

并不是所有的数据类型都可以指定直接量,能指定直接量的通常只有三种类型:
基本类型、字符串类型和null类型。

8种类型的直接量

具体而言,Java支持如下8种类型的直接量
int类型的直接量:在程序中直接给出的整型数值,可分为二进制、十进制、八进制和十六进制4种,其中二进制需要以0B或0b开头,八进制需要以0开头,十六进制需要以0x或0X开头。例如123、012(对应十进制的10)、0x12(对应十进制的18)等。
long类型的直接量:在整型数值后添加1或L后就变成了long类型的直接量。例如3L0x12L
float类型的直接量:在一个浮点数后添加f或F就变成了foat类型的直接量,这个浮点数可以是标准小数形式,也可以是科学计数法形式。例如5.34F3.14E5f
double类型的直接量:直接给出一个标准小数形式或者科学计数法形式的浮点数就是double类型的直接量。例如5.343.14E5
boolean类型的直接量:这个类型的直接量只有truefalse
char类型的直接量:char类型的直接量有三种形式,分别是用单引号括起来的字符、转义字符和Unicode值表示的字符。例如a’、\n‘和”\u0061.
String类型的直接量:一个用双引号括起来的字符序列就是Sing类型的直接量
null类型的直接量:这个类型的直接量只有一个值,即null.

在上面的8种类型的直接量中,null类型是一种特殊类型,它只有一个值:nu,而且这个直接量可以赋给任何引用类型的变量,用以表示这个引用类型变量中保存的地址为空,即还未指向任何有效对象。

3.5 基本类型的类型转换

Java程序中,不同的基本类型的值经常需要进行相互转换。Java语言所提供的7种数值类型之间可以相互转换,有两种类型转换方式:自动类型转换强制类型转换

3.5.1 自动类型转换

Java所有的数值型变量可以相互转换,如果系统支持把某种基本类型的值直接赋给另一种基本类型的变量,则这种方式被称为自动类型转换。
当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,系统将可以进行自动类型转换;
否则就需要强制转换。
表数范围小的可以向表数范围大的进行自动类型转换,就如同有两瓶水,当把小瓶里的水倒入大瓶中时不会有任何问题。Java支持自动类型转换的类型如图3.10所示。
这里有一张图片

1
2
3
        char

byte→short→int→long→float→double

图3.10中所示的箭头左边的数值类型可以自动类型转换为箭头右边的数值类型。

程序示例 自动类型转换

下面程序示范了自动类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class AutoConversion {
public static void main(String[] args) {
int a = 6;
// int可以自动转换为float类型
float f = a;
// 下面将输出6.0
System.out.println(f);
// 定义一个byte类型的整数变量
byte b = 9;
// 下面代码将出错,byte型不能自动类型转换为char型
// char c = b;
// 下面是byte型变量可以自动类型转换为double型
double d = b;
// 下面将输出9.0
System.out.println(d);
}
}

基本类型转字符串: 连接一个空字符串

不仅如此,当把任何基本类型的值和字符串值进行连接运算时,基本类型的值将自动类型转换为字符串类型,虽然字符串类型不是基本类型,而是引用类型。因此,如果希望把基本类型的值转换为对应的字符串时,可以把基本类型的值和一个空字符串进行连接

+不仅可作为加法运算符使用,还可作为字符串连接运算符使用。

程序示例 字符串和数值的连接运算

看如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class PrimitiveAndString
{
public static void main(String[] args)
{
// 下面代码是错的,因为5是一个整数,不能直接赋给一个字符串
// String str1 = 5;
// 一个基本类型值和字符串进行连接运算时,基本类型值自动转换为字符串
String str2 = 3.5f + "";
// 下面输出3.5
System.out.println(str2);
// 下面语句输出7Hello!
System.out.println(3 + 4 + "Hello!");
// 下面语句输出Hello!34,因为Hello! + 3会把3当成字符串处理,
// 而后再把4当成字符串处理
System.out.println("Hello!" + 3 + 4);
}
}

上面程序中有一个“3+4+"Helo!"”表达式,这个表达式先执行“3+4”运算,这是执行两个整数之间的加法,得到7,然后进行“7+"Helo!"”运算,此时会把7当成字符串进行处理,从而得到7Helo!
反之,对于“"Helo!"+3+4”表达式,先进行“"Hell!"+3”运算,得到一个Hello!3字符串,再和4进行连接运算,4也被转换成字符串进行处理。

3.5.2 强制类型转换

这里有一张图片
如果希望把图3.10中箭头右边的类型转换为左边的类型,则必须进行强制类型转换。
强制类型转换的语法格式是:(targetType)value,强制类型转换的运算符是圆括号()。当进行强制类型转换时,类似于把一个大瓶子里的水倒入一个小瓶子,如果大瓶子里的水不多还好,但如果大瓶子里的水很多,将会引起溢岀,从而造成数据丢失。
这种转换也被称为“缩小转换(Narrow Conversion)”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NarrowConversion {
public static void main(String[] args) {
int iValue = 233;
// 强制把一个int类型的值转换为byte类型的值
byte bValue = (byte) iValue;
// 将输出-23
System.out.println(bValue);
double dValue = 3.98;
// 强制把一个double类型的值转换为int
int tol = (int) dValue;
// 将输出3
System.out.println(tol);
}
}

在上面程序中,把一个浮点数强制类型转换为整数时,Java将直接截断浮点数的小数部分。除此之外,上面程序还把223强制类型转换为byte类型的整数,从而变成了23,这就是典型的溢出。图3.11示范了这个转换过程。
这里有一张图片
从图3.11可以看出,32位int类型的233在内存中如图3.11上面所示,强制类型转换为8位的byte类型,则需要截断前面的24位,只保留右边8位,最左边的1是一个符号位,此处表明这是一个负数,负数在计算机里是以补码形式存在的,因此还需要换算成原码

补码转原码

将补码减1得到反码形式,再将反码取反就可以得到原码。

原码 加权展开 得到十进制

最后的二进制原码为100101,这个byte类型的值为-(16+4+2+1),也就是-23

从图3.11很容易看出,当试图强制把表数范围大的类型转换为表数范围小的类型时,必须格外小,因为非常容易引起信息丢失。

程序示例 生成随机字符串

经常上网的读者可能会发现有些网页上会包含临时生成的验证字符串,那么这个随机字符串是如何生成的呢?可以先随机生成一个在指定范围内的int数字(如果希望生成小写字母,就在97~122之间),然后将其强制转换成char类型,再将多次生成的字符连缀起来即可。

下面程序示范了如何生成一个6位的随机字符串,这个程序中用到了后面的循环控制,不理解循环的读者可以参考后面章节的介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RandomStr {
public static void main(String[] args) {
// 定义一个空字符串
String result = "";
// 进行6次循环
for (int i = 0; i < 6; i++) {
// 生成一个97~122的int型的整数
int intVal = (int) (Math.random() * 26 + 97);
// 将intValue强制转换为char后连接到result后面
result = result + (char) intVal;
}
// 输出随机字符串
System.out.println(result);
}
}

小数直接量默认是double

下面一行容易出错的代码:

1
2
//直接把5.6赋值给f1oat类型变量将出现错误,因为5.6默认是 double类型
float a = 5.6;

上面代码中的5.6默认是一个double类型的浮点数,因此将5.6赋值给一个foat类型变量将导致错误,必须使用强制类型转换才可以,即将上面代码改为如下形式:

1
float a = (float) 5.6;

字符串数字转基本类型数字

在通常情况下,字符串不能直接转换为基本类型,但通过基本类型对应的包装类则可以实现把字符串转换成基本类型。例如,把字符串转换成int类型,则可通过如下代码实现:

1
2
3
String a ="45";
//使用 Integer的方法将一个字符串转换成int类型
int ivalue =Integer.parseInt(a);

Java为8种基本类型都提供了对应的包装类,8个包装类都提供了一个parseXxx(String str)静态方法用于将字符串转换成基本类型。关于包装类的介绍,请参考本书第6章

3.5.3 表达式类型的自动提升

一个算术表达式中包含多个基本类型的值时,整个算术表达式的数据类型将发生自动提升。Java定义了如下的自动提升规则:
所有的byte类型、short类型和char类型将被提升到int类型。
**整个算术表达式的数据类型自动提升到与表达式中最高等级操作数同样的类型**。操作数的等级排列如图3.10所示:
这里有一张图片
位于箭头右边类型的等级高于位于箭头左边类型的等级。

程序示例

下面程序示范了一个典型的错误:

1
2
3
4
5
6
7
// 定义一个short类型变量
short sValue = 5;
// 下面代码将出错:
// 右边表达式中在最高等级操作数为2(int)
// 表达式中的sValue将自动提升到int类型,
// 则右边的表达式类型为int,将一个int类型赋给short类型的变量将发生错误。
sValue = sValue - 2;

上面的“sValue-2”表达式的类型将被提升到int类型,这样就把右边的int类型值赋给左边的short类型变量,从而引起错误。
下面代码是表达式类型自动提升的正确示例代码:

1
2
3
4
5
6
7
8
9
byte b = 40;
char c = 'a';
int i = 23;
double d = .314;
// 右边表达式中在最高等级操作数为d(double型)
// 则右边表达式的类型为double型,故赋给一个double型变量
double result = b + c + i * d;
// 将输出144.222
System.out.println(result);

整数除法会丢弃小数部分

必须指出,表达式的类型将严格保持和表达式中最高等级操作数相同的类型。下面代码中两个int类型整数进行除法运算,即使无法除尽,也将得到一个int类型结果:

1
2
3
4
5
int val = 3;
// 右边表达式中2个操作数都是int,故右边表达式的类型为int
// 因此,虽然23/3不能除尽,依然得到一个int整数
int intResult = 23 / val;
System.out.println(intResult); // 将输出7

从上面程序中可以看出,当两个整数进行除法运算时,如果不能整除,得到的结果将是把小数部分截断取整后的整数

数值和字符串的加法

如果表达式中包含了字符串,则又是另一番情形了。因为当把加号(+)放在字符串和基本类型值之间时,这个加号是一个字符串连接运算符,而不是进行加法运算。看如下代码:

1
2
3
4
// 输出字符串Hello!a7
System.out.println("Hello!" + 'a' + 7);
// 输出字符串104Hello!
System.out.println('a' + 7 + "Hello!");

对于第一个表达式“"Hello!" + 'a' + 7”,先进行“"Hello!"+a”运算,把a转换成字符串,拼接成字符串Hello!a,接着进行“"Hello!"+7”运算,这也是一个字符串连接运算,得到结果是Hello!a7
对于第二个表达式,先进行“’a’+7”加法运算,其中’a’自动提升到int类型,变成a对应的ASCII值97,“97+7”将得到104,然后进行“104+”Hello!“”运算,104会自动转换成字符串,将变成两个字符串的连接运算,从而得到104Hello!

3.4 基本数据类型

Java的基本数据类型分为两大类:boolean类型和数值类型。而数值类型又可以分为整数类型和浮点类型,整数类型里的字符类型也可被单独对待。因此常把Java里的基本数据类型分为4类,如图3.8所示。
这里有一张图片
Java只包含这8种基本数据类型,值得指出的是,字符串不是基本数据类型,字符串是一个类,也就是一个引用数据类型。

3.4.1 整型

通常所说的整型,实际指的是如下4种类型

类型 描述
byte 一个byte类型整数在内存里占8位,表数范围是:-128(-2^7)~127(2^7-1)。
short 一个short类型整数在内存里占16位,表数范围是:-32768(-2^15)~32767(2^15-1)
int 一个int类型整数在内存里占32位,表数范围是:-2147483648(-2^31)~2147483647(2^31-1)
long 一个long类型整数在内存里占64位,表数范围是:(-2^63)~(2^63-1)。

整数直接量默认为int类型

int是最常用的整数类型,因此在通常情况下,直接给出一个整数值默认就是int类型。除此之外有如下两种情形必须指出。

  1. 如果直接将一个较小的整数值(在byteshort类型的表数范围内)赋给一个byteshrt变量,系统会自动把这个整数值当成byte或者short类型来处理。
  2. 如果使用一个巨大的整数值(超出了int类型的表数范围)时,Java不会自动把这个整数值当成long类型来处理。如果希望系统把一个整数值当成long类型来处理,应在这个整数值后增加l或者L作为后缀。通常推荐使用大写的L,因为英文小写的l很容易跟数字1搞混。

程序示例

下面的代码片段验证了上面的结论:

1
2
3
4
5
6
7
8
9
// 下面代码是正确的,系统会自动把56当成byte类型处理
byte a = 56;
/*
下面代码是错的,系统不会把9999999999999当成long类型处理,
所以超出int的表数范围,从而引起错误
*/
// long bigValue = 9999999999999;
// 下面代码是正确的,在巨大的整数值后使用L后缀,强制使用long类型
long bigValue2 = 9223372036854775807L;

可以把一个较小的整数值(在int类型的表数范围以内)直接赋给一个long类型的变量,这并不是因为Java会把这个较小的整数值当成long类型来处理,Java依然把这个整数值当成int类型来处理,只是因为int类型的值会自动类型转换到long类型

整数的二进制 八进制 十进制 十六进制表示

Java中整数值有4种表示方式:十进制、二进制、八进制和十六进制,其中:

  • 二进制的整数以0b0B开头;
  • 八进制的整数以0开头;
  • 十六进制的整数以0x或者0X开头,其中10-15分别以a-f(此处的a-f不区分大小写)来表示。

程序示例 定义八进制整数 定义十六进制整数

下面的代码片段分别使用八进制和十六进制的数。

1
2
3
4
5
6
7
8
9
// 以0开头的整数值是8进制的整数
int octalValue = 013;
System.out.println(octalValue);

// 以0x或0X开头的整数值是16进制的整数
int hexValue1 = 0x13;
int hexValue2 = 0XaF;
System.out.println(hexValue1);
System.out.println(hexValue2);

在某些时候,程序需要直接使用二进制整数,二进制整数更“真实”,更能表达整数在内存中的存在形式。不仅如此,有些程序(尤其在开发一些游戏时)使用二进制整数会更便捷

程序示例 定义二进制整数

Java7开始新增了对二进制整数的支持,二进制的整数以0b或者0B开头。程序片段如下:

1
2
3
4
5
6
7
8
// 定义二个8位的二进制数
int binVal1 = 0b11010100;
byte binVal2 = 0B01101001;
// 定义一个32位的二进制数,最高位是符号位。
int binVal3 = 0B10000000000000000000000000000011;
System.out.println(binVal1); // 输出212
System.out.println(binVal2); // 输出105
System.out.println(binVal3); // 输出-2147483645

当定义32位的二进制整数时,最高位其实是符号位,当符号位是1时,表明它是一个负数,负数在计算机里是以补码的形式存在的,因此还需要换算成原码

原码

所有数字在计算机底层都是以二进制形式存在的,原码是直接将一个数值换算成二进制数。

反码

反码是对原码按位取反,只是最高位(符号位)保持不变

补码的计算规则

计算机以补码的形式保存所有的整欻。

  • 正数的补码和原码完全相同,
  • 负数的补码是其反码加1;

二进制整数转成十进制整数

  • 补码减一得到原码
  • 原码符号位不变,其他位按位取反得到原码
  • 根据原码使用展开式计算

将上面的二进制整数binVal3转换成十进制数的过程如图3.9所示。
这里有一张图片

二进制整数默认是int类型

正如前面所指出的,整数值默认就是int类型,因此使用二进制形式定义整数时,二进制整数默认占32位,其中第32位是符号位;
如果在二进制整数后添加l或L后缀,那么这个二进制整数默认占64位,其中第64位是符号位
例如如下程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
定义一个8位的二进制,该数值默认占32位,因此它是一个正数。
只是强制类型转换成byte时产生了溢出,最终导致binVal4变成了-23
*/
byte binVal4 = (byte)0b11101001;
/*
定义一个32位的二进制数,最高位是1。
但由于数值后添加了L后缀,因此该整数的实际占64位,第32位的1不是符号位。
因此binVal5的值等于2的31次方 + 2 + 1
*/
long binVal5 = 0B10000000000000000000000000000011L;
System.out.println(binVal4); // 输出-23
System.out.println(binVal5); // 输出2147483651

只是在定义binVal5这个二进制整数时添加了L后缀,这就表明把它当成long类型处理,因此该整数实际占64位。此时的第32位不再是符号位,因此它依然是一个正数
至于程序中定义binVal4的代码,其中0b11101001依然是一个32位的正整数,只是程序进行强制类型转换时发生了溢出,导致它变成了负数。

3.4.2 字符型

字符型通常用于表示单个的字符,字符型值必须使用单引号('')括起来。Java语言使用16位的Unicode字符集作为编码方式,而Unicode被设计成支持世界上所有书面语言的字符,包括中文字符,因此Java程序支持各种语言的字符。

字符型直接量的三种形式

字符型值有如下三种表示形式:

  • 直接通过单个字符来指定字符型值,例如'A''9''0'等。
  • 通过转义字符表示特殊字符型值,例如'\n'\t等。
  • 直接使用Unicode值来表示字符型值,格式是\uXXXX,其中XXXX代表一个十六进制的整数

Java语言中常用的转义字符如表3.2所示。

转义字符 说明 Unicode表示方式
\b 退格符 \u0008
\n 换行符 \u000a
\r 回车符 \u000d
\t 制表符 \u0009
\" 双引号 \u0022
\' 单引号 \u0027
\\ 反斜线 \u005c

字符型值也可以采用十六进制编码方式来表示,范围是”\u0000\uFFFF‘,一共可以表示65536个字符,其中前256个(“\u0000\u00FF)字符和ASCI码中的字符完全重合。

char可以当成整型使用

由于计算机底层保存字符时,实际是保存该字符对应的编号,因此char类型的值也可直接作为整型值来使用,它相当于一个16位的无符号整数,表数范围是0~65535

char类型的变量、值完全可以参与加、减、乘、除等数学运算,也可以比较大小实际上都是用该字符对应的编码参与运算。
如果把0~65535范围内的一个int整数赋给char类型变量,系统会自动把这个int整数当成char类型来处理。

程序示例 字符型变量

下面程序简单示范了字符型变量的用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CharTest {
public static void main(String[] args) {
// 直接指定单个字符作为字符值
char aChar = 'a';
// 使用转义字符来作为字符值
char enterChar = '\r';
// 使用Unicode编码值来指定字符值
char ch = '\u9999';
// 将输出一个'香'字符
System.out.println(ch);
// 定义一个'疯'字符值
char zhong = '疯';
// 直接将一个char变量当成int类型变量使用
int zhongValue = zhong;
System.out.println(zhongValue);
// 直接把一个0~65535范围内的int整数赋给一个char变量
char c = 97;
System.out.println(c);
}
}

Java没有提供表示字符串的基本数据类型,而是通过String类来表示字符串,由于字符串由多个字符组成,因此字符串要使用双引号括起来。如下代码:

1
2
//下面代码定义了一个s变量,它是一个字符串实例的引用,它是一个引用类型的变量
String s="沧海月明珠有泪,蓝田玉暖日生烟。";

char类型使用单引号 字符串使用双引号

char类型使用单引号括起来,而字符串使用双引号括起来

值得指出的是,Java语言中的单引号双引号反斜线都有特殊的用途,如果一个字符串中包含了这些特殊字符,则应该使用转义字符的表示形式。
例如,在Java程序中表示一个绝对路径:"c:\Codes",但这种写法得不到期望的结果,因为Java会把反斜线当成转义字符,所以应该写成这种形式:"c:\\codes",只有同时写两个反斜线,Java才会把第一个反斜线当成转义字符,和后一个反斜线组成真正的反斜线

3.4.3 浮点型

Java的浮点类型有两种:floatdoubleJava的浮点类型有固定的表数范围和字段长度,字段长度和表数范围与机器无关。
Java的浮点数遵循IEEE 754标准,采用二进制数据的科学计数法来表示浮点数,

  • 对于float型数值,第1位是符号位,接下来8位表示指数,再接下来的23位表示尾数;
  • 对于double类型数值,第1位也是符号位,接下来的11位表示指数,再接下来的52位表示尾数。

float和double不精确

因为Java浮点数使用二进制数据的科学计数法来表示浮点数,因此可能不能精确表示一个浮点数。例如把5.2345556f值赋给一个float类型变量,接着输出这个变量时看到这个变量的值已经发生了改变。
使用double类型的浮点数比float类型的浮点数更精确,但如果浮点数的精度足够高(小数点后的数字很多时),依然可能发生这种情况。如果开发者需要精确保存一个浮点数,则可以考虑使用BigDecimal
double类型代表双精度浮点数,foat类型代表单精度浮点数。一个double类型的数值占8字节、64位,一个foat类型的数值占4字节、32位。

浮点数表示形式

Java语言的浮点数有两种表示形式

  • 十进制数形式:这种形式就是简单的浮点数,例如5.12512.0.512。浮点数必须包含一个小数点,否则会被当成int类型处理。
  • 科学计数法形式:例如5.12e2(即5.12×10^2),5.12E2(也是5.12×10^2)。

浮点数才可以使用科学计数法

必须指出的是,只有浮点类型的数值才可以使用科学计数法形式表示。例如,51200是一个int类型的值,但512E2则是浮点类型的值

java浮点数默认是double

Java语言的浮点类型默认是double类型,如果希望Java把一个浮点类型值当成foat类型处理,应该在这个浮点类型值后加上后缀f或F。例如:5.12代表一个double类型的值,占64位的内存空间;5.12f或者5.12F才表示一个foat类型的值,占32位的内存空间。当然,也可以在一个浮点数后添加d或D后缀,强制指定是double类型,但通常没必要

正无穷大 负无穷大 非数

Java还提供了三个特殊的浮点数值:正无穷大负无穷大非数,用于表示溢出和出错。
例如:

  • 使用一个正数除以0将得到正无穷大,

  • 使用一个负数除以0将得到负无穷大,

  • 0.0除以0.0或对一个负数开方将得到一个非数。

  • 正无穷大通过DoubleFloat类的POSITIVE_INFINITY表示;

  • 负无穷大通过DoubleFoat类的NEGATIVE_INFINITY表示,

  • 非数通过DoubleFloat类的NaN表示。

正无穷等于正无穷 负无穷等于负无穷 非数不等于任何数 非数不等于非数

所有的正无穷大数值都是相等的,所有的负无穷大数值都是相等的;NaN不与任何数值相等,甚至和NaN都不相等

浮点数除0得到无穷大 整数除零抛出算术异常

注意,只有浮点数除以0才可以得到正无穷大或负无穷大,因为Java语言会自动把和浮点数运算的0(整数)当成00(浮点数)处理。
如果一个整数值除以0,则会拋出一个异常:ArithmeticException:/ByZero(除以0异常)

程序示例

下面程序示范了上面介绍的关于浮点数的各个知识点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class FloatTest {
public static void main(String[] args) {
float af = 5.2345556f;
// 下面将看到af的值已经发生了改变
System.out.println(af);
double a = 0.0;
double c = Double.NEGATIVE_INFINITY;
float d = Float.NEGATIVE_INFINITY;
// 看到float和double的负无穷大是相等的。
System.out.println(c == d);
// 0.0除以0.0将出现非数
System.out.println(a / a);
// 两个非数之间是不相等的
System.out.println(a / a == Float.NaN);
// 所有正无穷大都是相等的
System.out.println(6.0 / 0 == 555.0 / 0);
// 负数除以0.0得到负无穷大
System.out.println(-8 / a);
// 下面代码将抛出除以0的异常
// System.out.println(0 / 0);
}
}

运行结果:

1
2
3
4
5
6
5.2345557
true
NaN
false
true
-Infinity

3.4.4 数值中使用下画线分隔

正如前面程序中看到的,当程序中用到的数值位数特别多时,程序员眼睛“看花”了都看不清到底有多少位数。为了解决这种问题,Java7引入了一个新功能:程序员可以在数值中使用下画线,不管是整型数值,还是浮点型数值,都可以自由地使用下画线。通过使用下画线分隔,可以更直观地分辨数值中到底包含多少位。如下面程序所示。

1
2
3
4
5
6
7
8
9
10
11
public class UnderscoreTest {
public static void main(String[] args) {
// 定义一个32位的二进制数,最高位是符号位。
int binVal = 0B1000_0000_0000_0000_0000_0000_0000_0011;
double pi = 3.14_15_92_65_36;
System.out.println(binVal);
System.out.println(pi);
double height = 8_8_4_8.23;
System.out.println(height);
}
}

3.4.5 布尔型

布尔型只有一个boolean类型,用于表示逻辑上的“真”或“假”。在Java语言中,boolean类型的数值只能是truefalse,不能用0或者非0来代表。其他基本数据类型的值也不能转换成boolean类型。

Java规范并没有强制指定boolean类型的变量所占用的内存空间。虽然**boolean类型的变量或值只要1位即可保存**,但由于大部分计算机在分配内存时允许分配的最小内存单元是字节(8位),因此**bit大部分时候实际上占用8位**
例如,下面代码定义了两个boolean类型的变量,并指定初始值。

1
2
3
4
//定义b1的值为true
boolean bl=true;
//定义b2的值为 false
boolean b2=false;

字符串"true""false"不会直接转换成boolean类型,但如果使用一个boolean类型的值和字符串进行连接运算,则boolean类型的值将会自动转换成字符串。看下面代码(程序清单同上):

1
2
3
4
5
6
// 下面代码将出现错误:字符串不能直接变成boolean型的值
// boolean b3 = "true";
// 使用boolean和字符串进行连接运算,boolean会自动转换成字符串
String str = true + "";
// 下面将输出true
System.out.println(str);

boolean类型的值或变量主要用做旗标来进行流程控制,Java语言中使用boolean类型的变量或值控制的流程主要有如下几种。

  • if条件控制语句

  • while循环控制语句

  • do while循环控制语句

  • for循环控制语句

  • 除此之外,boolean类型的变量和值还可在三目运算符(?:)中使用

3.3 数据类型分类

变量的本质

编程的本质,就是对内存中数据的访问和修改
程序所用的数据都会保存在内存中,变量是一种用来访问或修改内存中数据的机制,每个变量都代表了某一小块内存,而且变量是有名字的,
程序对变量赋值,实际上就是把数据装入该变量所代表的内存区的过程;
程序读取变量的值,实际上就是从该变量所代表的内存区取值的过程。
形象地理解:变量相当于一个有名称的容器,该容器用于装各种不同类型的据。

Java语言是强类型语言

Java语言是强类型(strongly Typed)语言,强类型包含两方面的含义:

  1. 所有的变量必须先声明后使用;
  2. 指定类型的变量只能接受类型与之匹配的值

这意味着每个变量和每个表达式都有一个在编译时就确定的类型。类型限制了一个变量能被赋的值,限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作,并确定了这些操作的含义。
强类型语言可以在编译时进行更严格的语法检査,从而减少编程错误。

声明变量的语法

声明变量的语法非常简单,只要指定变量的类型和变量名即可,如下所示:

1
type varName [=初始值];

上面语法中,定义变量时既可指定初始值,也可不指定初始值。随着变量的作用范围的不同,变量还可能使用其他修饰符。但不管是哪种变量,定义变量至少需要指定变量类型和变量名两个部分。定义变量时的变量类型可以是Java语言支持的所有类型

java变量类型分类

Java语言支持的类型分为两类:
基本类型(PrimitiveType)和引用类型(ReferenceType)。

基本类型

基本类型包括boolean类型和数值类型数值类型有整数类型和浮点类型。整数类型包括byteshortintlongchar。其中char代表字符型,实际上字符型也是一种整数类型,相当于无符号整数类型。浮点类型包括foatdouble

引用类型

引用类型包括接口数组类型,还有一种特殊的**null类型**。
所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针,只是Java语言里不再使用指针这个说法。

空类型

空类型(null Type)就是null值的类型,这种类型没有名称。因为null类型没有名称,所以不可能声明一个null类型的变量或者转换到null类型。空引用(null)是null类型变量唯一的值。空引用(null可以转换为任何引用类型。
在实际开发中,程序员可以忽略null类型,假定null只是引用类型的一个特殊直接量。
空引用(null)只能被转换成引用类型,不能转换成基本类型,因此不要把一个null值赋给基本数据类型的变量

3.2.3 Java关键字

Java语言中有一些具有特殊用途的单词被称为关键字(keyword),当定义标识符时,不要让标识符和关键字相同,否则将引起错误。例如,下面代码将无法通过编译:

1
2
//试图定义一个名为 boolean的变量,但 boolean是关键字,不能作为标识符
int boolean;

所有的关键字都是小写的

Java的所有关键字都是小写的,TRUEFALSENULL都不是Java关键字。

Java关键字汇总表

Java一共包含50个关键字,如下表所示

abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while

上面的50个关键字中,enum是从Java5新增的关键字,用于定义一个枚举。

保留字

gotoconst这两个关键字也被称为保留字(reserved Word),保留字的意思是,Java现在还未使用这两个关键字,但可能在未来的Java版本中使用这两个关键字;

标识符不能是直接量

不仅如此,Java还提供了三个特殊的直接量(literal):truefalsenull
Java语言的标识符也不能使用这三个特殊的直接量。

3.2.2 Java9的标识符规则

什么是标识符

标识符就是用于给程序中变量、类、方法命名的符号

java标识符规则

Java语言的标识符必须以字母下画线(_)、美元符($)开头,后面可以跟任意数目的字母数字下画线()和美元符($)。

此处的字母并不局限于26个英文字母,甚至可以包含中文字符、日文字符等。
由于Java9支持Unicode 8.0字符集,因此Java的标识符可以使用Unicode 8.0所能表示的多种语言的字符。
Java语言是区分大小写的,因此abcAbc是两个不同的标识符。

java标识符规则

使用标识符时,需要注意如下规则:

  • 标识符可以由字母数字下画线_美元符($)组成,其中数字不能打头
  • 标识符不能是Java``关键字保留字,但可以包含关键字和保留字。
  • 标识符不能包含空格
  • 标识符只能包含美元符($),不能包含@#等其他特殊字符。

3.2 标识符和关键字

Java语言也和其他编程语言一样,使用标识符作为变量、对象的名字,也提供了系列的关键字用以实现特别的功能。本节详细介绍Java语言的标识符和关键字等内容。

3.2.1 分隔符

Java语言里的分号;、花括号{}、方括号[]、圆括号()、空格、圆点.都具有特殊的分隔作用,因此被统称为分隔符。

1. 分号

Java语言里对语句的分隔不是使用回车来完成的,Java语言采用分号(;)作为语句的分隔,因此每个Java语句必须使用分号作为结尾Java程序允许一行书写多个语句,每个语句之间以分号隔开即可;一个语句也可以跨多行,只要在最后结束的地方使用分号结束即可。

字符串 变量名不能跨行

值得指出的是,Java语句可以跨越多行书写,但一个字符串、变量名不能跨越多行。例如,下面的Java语句是错误的。

1
2
3
4
5
6
//字符串不能跨越多行
string a="ddddd
xxxxx";
//变量名不能跨越多行
string na
me="小明";

不仅如此,虽然Java语法允许一行书写多个语句,但从程序可读性角度来看,应该避免在一行书写多个语句

2. 花括号

**花括号的作用就是定义一个代码块,**一个代码块指的就是“{”和“}”所包含的一段代码,代码块在逻辑上是一个整体。
Java语言而言,类定义部分必须放在一个代码块里,方法体部分也必须放在个代码块里。
除此之外,条件语句中的条件执行体和循环语句中的循环体通常也放在代码块里。

花括号一般是成对出现的,有一个“{”则必然有一个“}”,反之亦然。

3. 方括号

方括号的主要作用是用于访问数组元素,方括号通常紧跟数组变量名,而方括号里指定希望访问的数组元素的索引。
例如,如下代码:

1
2
//下面代码试图为名为a的数组的第四个元素赋值
a[3]=3;

4. 圆括号

圆括号是一个功能非常丰富的分隔符:

  • 定义方法时必须使用圆括号来包含所有的形参声明,
  • 调用方法时也必须使用圆括号来传入实参值;
  • 圆括号还可以将表达式中某个部分括成一个整体,保证这个部分优先计算;
  • 圆括号还可以作为强制类型转换的运算符。

5. 空格

Java语言使用空格将一条语句分隔成不同部分Java语言是一门格式自由的语言,所以空格几乎可以出现在Java程序的任何地方,也可以出现任意多个空格,但不要使用空格把一个变量名隔开成两个,这将导致程序出错。
Java语言中的空格包含
空格符
(Space)、制表符(Tab)和回车(Enter)等。
除此之外,Java源程序还会使用空格来合理缩进Java代码,从而提供更好的可读性。

6. 圆点

圆点.通常用作类对象和它的成员之间的分隔符,表明调用某个类或某个实例的指定成员
成员包括成员变量、方法和内部类

3.1.2 Java9增强文档注释

Java语言还提供了一种功能更强大的注释形式:文档注释。如果编写Java源代码时添加了合适的文档注释,然后通过JDK提供的javadoc工具可以直接将源代码里的文档注释提取成一份系统的API文档
Java提供了大量的基础类,因此Oracle也为这些基础类提供了相应的API文档,用于告诉开发者如何使用这些类,以及这些类里包含的方法。

下载API文档

进入java SE下载页面:
https://www.oracle.com/technetwork/java/javase/downloads/index.html
将页面拉到最下面就可以看到文档的下载链接

1
https://www.oracle.com/technetwork/java/javase/documentation/jdk11-doc-downloads-5097203.html

然后进入文档下载页面
Java9API文档进行了增强,Java9API文档增加了一个搜索框,如图3.2中右上角所示,用户可以通过该搜索框快速查找指定的Java类。

Java9的API子集

Java 9API文档分成3个子集。

  • Java SE:该子集的API文档主要包含Java SE的各种类
  • JDK:该子集的API文档主要包含JDK的各种工具类
  • Java FX:该子集的API文档主要包含Java FX的各种类

哪些文档注释会被javadoc处理

由于文档注释是用于生成API文档的,而API文档主要用于说明类、方法、成员变量的功能。因此,javadoc工具只处理文档源文件在接口方法成员变量构造器内部类之前的注释,忽略其他地方的文档注释
而且**javadoc工具默认只处理以publicprotected修饰的类、接口、方法、成员变量、构造器和内部类之前的文档注释**。

API文档类似于产品的使用说明书,通常使用说明书只需要介绍那些暴露的、供用户使用的部分。Java类中只有以publicprotected修饰的内容才是希望暴露给別人使用的内容,因此javadoc默认只处理publicprotected修饰的内容。

如何提取私有成员的文档注释

如果开发者确实希望javadoc工具可以提取private修饰的内容,则可以在使用javadoc工具时增加-private选项

文档注释符号

文档注释以斜线后紧跟两个星号(/**)开始,以星号后紧跟一个斜线(*/)结束,中间部分全部都是文档注释,会被提取到API文档中。
Java9API文档已经支持HTML5规范,因此为了得到完全兼容HTML5API文档,必须保证文档注释中的内容完全兼容HTML5规范。

生成API

编写好上面的Java程序后,就可以使用javadoc工具提取这程序中的文档注释来生成API文档了。javadoc命令的基本用法如下:

javadoc语法格式

1
javadoc 选项 Java源文件|包

javadoc命令可对源文件生成API文档.

javadoc选项

在上面的语法格式中,Java源文件可以支持通配符,例如,使用*.java来代表当前路径下所有的Java源文件。javadoc的常用选项有如下几个。

  • -d <Directory>:该选项用于指定保存生成的API文档的目录下。
  • -windowtitle <text>:该选项指定一个字符串,用于设置API文档的浏览器窗口标题
  • -doctitle <html-code>:该选项指定一个HTML格式的文本,用于指定概述页面的标题。只有对处于多个包下的源文件来生成API文档时,才有概述页面。
  • -header <html-code>:该选项指定一个HTML格式的文本,包含每个页面的页眉

查看javadoc帮助文档

除此之外,javadoc命令还包含了大量其他选项,读者可以通过在命令行窗口执行javadoc -help来查看javadoc命令的所有选项。

在命令行窗口执行如下命令来为刚刚编写的两个Java程序生成API文档:

1
javadoc -d apidoc -windowtitle 测试 -doctitle 学习 javadoc 工具的测试 API文档 -header 我的类 *Test.java

常用javadoc标记

除此之外,如果希望javadoc工具生成更详细的文档信息,例如为方法参数方法返回值等生成详细的说明信息,则可利用javadoc标记。常用的javadoc标记如下:

  • @author:指定Java程序的作者
  • @version:指定源文件的版本。
  • @deprecated:不推荐使用的方法。
  • @param:方法的参数说明信息
  • @return:方法的返回值说明信息。
  • @see:“参见”,用于指定交叉参考的内容。
  • @exception:抛出异常的类型。
  • @throws:抛出的异常,和@exception同义。

可以出现在 类或者接口 的文档注释

需要指出的是,这些标记的使用是有位置限制的。上面这些标记可以出现在类或者接口的文档注释中的有@see@deprecated@author@version等;

可以出现在 方法或构造器 的文档注释

可以出现在方法或构造器的文档注释中的有@see@deprecated@param@return@throws@exception等;

可以出现在成员变量的文档注释

可以出现在成员变量的文档注释中的有@see@deprecated等。

javadoc不会默认提取author和author注释

javadoc工具默认不会提取@author@author两个标记的信息,如果需要提取这两个标记的信息,应该在使用javadoc工具时指定-author-version两个选项。
为了能提取到文档中的@author@version等标记信息,在使用javadoc工具时增加-author-version两个选项,即按如下格式来运行javadoc命令:

1
javadoc -d apidoc -windowtitle 测试 -doctitle 学习 javadoc工具的测试 API 文档 -header 我的类 -version -author *Test.java

上面命令将会提取Java源程序中的authorvcrsion两个标记的信息。除此之外,还会提取@param@return标记的信息.

包描述文件

API文档中的包注释并不是直接放在Java源文件中的,而是必须另外指定,通常通过一个标准的HTML5文件来提供包注释,这个文件被称为包描述文件。包描述文件的文件名通常是package.html,并与该包下所有的Java源文件放在一起,javadoc工具会自动寻找对应的包描述文件,并提取该包描述文件中的<body>元素里的内容,作为该包的描述信息

程序示例

目录结构

1
2
3
4
5
6
7
8
G:\Desktop\随书源码\疯狂Java讲义(第4版)光盘\codes\03\3.1\package
├─first\
│ ├─JavadocTest.java
│ └─package.html
└─second\
├─JavadocTagTest.java
├─package.html
└─Test.java

该项目有两个文件夹(包):

  • first文件夹:包含**JavadocTestJava**文件(该Java类的包为first),对应包描述文件package.html
  • second文件夹:包含Test.java文件和JavadocTagTest.java文件(这两个Java类的包为yeku),对应包描述文件package.html

在命令行窗口进入firstsecond所在路径(package路径),执行如下命令:

1
javadoc -d apidoc -windowtitle 测试 -doctitle 学习javadoc工具的测试API文档 -header 我的类 -version -author first second

上面命令指定对first包和second包来生成API文档,而不是对Java源文件来生成API文档,这也是允许的。其中first包和second包下面都提供了对应的包描述文件。

如果需要设置包描述信息,则需要将Java源文件按包结构来组织存放,这不是问题。实际上,当编写Java源文件时,通常总会按包结构来组织存放Java源文件,这样更有利于项目的管理
现在生成的API文档已经非常“专业”了,和系统提供的APⅠ文档基本类似。关于Java文档注释和javadoc工具使用的介绍也基本告一段落了。

3.1 注释

程序注释是源代码的一个重要部分,对于一份规范的程序源代码而言,注释应该占到源代码的1/3以上。几乎所有的编程语言都提供了添加注释的方法。一般的编程语言都提供了基本的单行注释和多行注释,Java语言也不例外,除此之外,Java语言还提供了一种文档注释。

Java注释类型

Java语言的注释一共有三种类型

  • 单行注释。
  • 多行注释。
  • 文档注释。

3.1.1 单行注释和多行注释

单行注释

单行注释就是在程序中注释一行代码,在Java语言中,将双斜线(//)放在需要注释的内容之前就可以了。

多行注释

多行注释是指一次性地将程序中多行代码注释掉,在Java语言中,使用“/*”和“*/”将程序中需要注释的内容包含起来,“/*”表示注释开始,而“*/”表示注释结束。

使用注释来调试程序

除此之外,添加注释也是调试程序的一个重要方法。如果觉得某段代码可能有问题,可以先把这段代码注释起来,让编译器忽略这段代码,再次编译、运行,如果程序可以正常执行,则可以说明错误就是由这段代码引起的;如果依然出现相同的错误,则可以说明错误不是由这段代码引起的。