如果有需要这本书(第一卷)的中文版PDF,可以留下邮箱~
更加醒目的数字表示方法
从Java7开始,可以给数字字面量添加下划线,让数字更加易读,Java编译器会去除这些下划线。
// 给数值加下划线, 不影响数值本身
int num = 100_000_000;
System.out.println(num); // 100000000
浮点数的精度问题
浮点数值不适用于无法接受舍入误差的金融计算中。
System.out.println( 2.0-1.1 ) 将打印出0.8999999999999999, 而不是0.9。
这种舍入误差的主要原因是浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10。这就好像十进制无法精确地表示分数 1/3—样。
如果在数值计算中不允许有任何舍入误差,就应该使用BigDecimal类。
System.out.println(2.0-1.1); // 0.8999999999999999
在循环中,检测两个浮点数是否相等需要格外小心。下面的for循环可能永远不会结束,由于舍入的误差,最终可能得不到精确值。
for (double x = 0; x != 10; x += 0.1){...}
数学相关
下面是用于表示溢出和出错情况的三个特殊的浮点数值和其对应的常量(实际应用中很少用到):
- 正无穷大: Double.POSITIVE_INFINITY
- 负无穷大: Double.NEGATIVE_INFINITY
- NaN(不是一个数字): Double.NaN
正整数除以0的结果为正无穷大。计算 0/0 或者负数的平方根结果为 NaN。
// 正无穷大
System.out.println(Double.POSITIVE_INFINITY); // Infinity
// 负无穷大
System.out.println(Double.NEGATIVE_INFINITY); // -Infinity
// 非数值
System.out.println(Double.NaN); // NaN
// 一个正整数除以0为正无穷大
System.out.println(Double.isInfinite(1.0 / 0.0)); // true
// 0/0或负数的平方根为NaN
System.out.println(Double.isNaN(0.0 / 0.0)); // true
// 不能这样检测一个特定值是否等于 Double.NaN:
if (x == Double.NaN) // 永远都是false
// 所有“非数值”的值都认为是不相同的。然而,可以使用 Double.isNaN 方法:
if (Double.isNaN(Double.NaN)) // 永远都是false
一些数学符号和函数
Math类提供了一些常用的数学函数和常量
// Unicode转义字符, 表示希腊字母π
System.out.println('\u03C0'); // π
// 表示π的近似值
System.out.println(Math.PI); // 3.141592653589793
// 平方根
System.out.println(Math.sqrt(4)); // 2.0
// 幂运算
System.out.println(Math.pow(3, 2)); // 9.0
注释的语法错误
因为**\u开头**的字符表示Unicode转义字符,列如: 希腊字母π (’\u03c0’);这样造成了==注释中出现的\u可能会带来错误==。如:
// 经过测试, 下面这个注释在IDEA中不会报错
// \u00A0 is a newline
这样会产生一个语法错误,因为读程序时\u00A0会替换为一个换行符。
类似地还有下面这个注释:
// 经过测试, 下面这个注释在IDEA中独占一行时不会报错
// Look inside c:\users
也会产生一个语法错误,因为\u后面并未跟着4个十六进制数。
自增/自减运算符
建议不要在表达式中使用 ++/-- (自增运算符/自减运算符),因为这样的代码很容易让人困惑,而且可能会带来烦人的bug。
// 不建议类似如下的操作
int m = 7;
int n = 7;
int a = 2 * ++m; // 现在a为16, m为8
int b = 2 * n++; // 现在b为14, n为8
短路特性
&& (与) 和 || (或) 运算符是按照“短路”方式来求值的:
如果第一个操作已经能够确定表达式的值,第二个操作数就不必计算了。
& 和 | 运算符也会得到一个布尔值,这些运算符与&&和||运算符很类似,但是 & 和 | 运算符不采用“短路“方式来求值,也就是说,得到计算结果之前多个操作数都需要计算。
// 如果表达式1的结果为false, 那么结果不可能为true。因此表达式2就不必计算了。
表达式1 && 表达式2
// 如果表达式1的结果为true, 那么结果一定为true。因此表达式2也不必计算。
表达式1 || 表达式2
利用短路特性可以避免一些错误,如:
// 如果x等于0, 那么第二部分就不会计算。因此, 如果x为 0, 也就不会计算1/x, 除以0的错误就不会出现。
x != 0 && 1/x > x+y
// 如果是&运算符, 则这行代码会报错
x != 0 & 1/x > x+y // java.lang.ArithmeticException: / by zero
字符串的比较
在Java中使用equals方法来检测两个字符串是否相等
需要注意的是,==比较的是内存地址,而equals方法比较的是字面量。
调用equals方法时有一个小技巧:让字面量来调用该方法,这样就算变量为null,也不会造成空指针异常。
String greeting = "Hello";
// equals方法比较字符串的字面量
System.out.println("Hello".equals(greeting)); // true
// 可以用compareTo方法代替equals
System.out.println("Hello".compareTo(greeting) == 0); // true
// == 运算符比较字符串内存地址
System.out.println("Hello" == greeting); // 可能是true
System.out.println(greeting.substring(0,3) == "Hel") // 可能是false
一定不要使用==运算符检测两个字符串是否相等! 这个运算符只能够确定两个字串是否放置在同一个位置上。当然, 如果字符串放置在同一个位置上, 它们必然相等。但是,完全有可能将内容相同的多个字符串的拷贝放置在不同的位置上。
如果虚拟机始终将相同的字符串共享, 就可以使用 == 运算符检测是否相等。但实际上只有字符串常量是共享的,而 + 或 substring 等操作产生的结果并不是共享的。因此,千万不要使用 == 运算符测试字符串的相等性, 以免在程序中出现糟糕的 bug。
StringBuilder类与StringBuffer类
这两个类的API是相同的。
StringBuilder:效率高,单线程
StringBuffer:效率低,多线程
如果所有字符串在一个单线程中编辑(绝大部分情况是这样),则应该使用StringBuilder类。
安全的输入密码方式
因为输入是可见的,所以Scanner类不适合从控制台读取密码。JavaSE 6 特别引入了Console类实现这个目的。
采用 Console 对象处理输入不如采用 Scanner 方便。每次只能读取一行输入, 而没有能够读取一个单词或一个数值的方法。
经测试,需要在命令行状态下使用Console类的方法,而不能在IDE中使用,否则会报空指针异常。
// 需要在控制台中使用,可以在输入密码时不显示输入的内容
Console console = System.console();
String username = console.readLine("请输入账号: ");
// 为了安全起见, 返回的密码存放在一维字符数组中, 而不是字符串中。
char[] pwdChars = console.readPassword("请输入密码: ");
沿用C语言的格式化输出
Java SE 5.0 沿用了 C 语言库函数中的 printf方法。
// printf方法需要手动换行,'\n'为换行符
System.out.printf("[%s, %d cups of tea.]\n", "Hello", 3); // [Hello, 3 cups of tea.]
// 格式化输出88.888888: 总共7位包括小数点后3位,如果长度超出,则用空格填充
System.out.printf("[%7.3f]\n", 88.888888); // [ 88.889]
// 格式化输出8888888888(10个8): 保留位小数,并用逗号分组
System.out.printf("%,.3f\n", 8888888888.88); // [8,888,888,888.880]
文件输入与输出
要想对文件进行读取,就需要一个用File对象构造一个Scanner对象。如果文件名包含反斜杠符号,就需要在每个反斜杠之前再加一个额外的反斜杠
// 这里指定了UTF-8字符编码,如果省略字符编码,则会使用运行这个Java程序的机器的“默认编码”,如果在不同的机器上运行这个程序,可能会有不同的表现。
Scanner fileScanner = new Scanner(Paths.get("src/1.txt"), "UTF-8");
// 循环读取文件内容
while (fileScanner.hasNext()) {
System.out.println(fileScanner.nextLine());
}
要想写入文件,就需要构造一个PrintWriter对象。在构造器中,只需要提供文件名。如果文件不存在,自动创建该文件。可以像输出到System.out一样使用print、println以及printf命令。
// 写入文件 (覆盖写入)
PrintWriter printWriter = new PrintWriter("src/1.txt", "UTF-8");
printWriter.println("The first new line");
printWriter.println("The second new line");
// 调用这些方法并不是直接把数据写入文件,而是先写入了缓冲区,需要调用flush方法将缓冲区的数据写入文件。
printWriter.flush();
麻烦的相对路径
相对路径很容易写错,因为它是由IDE控制,所以在写相对路径时可以先查看当前的路径位置(JVM的运行位置)。
String dir = System.getProperty("user.dir");
“块”中的变量定义
在 C++ 中,可以在嵌套的块中重定义一个变量。在内层定义的变量会覆盖在外层定义的变量。这样,有可能会导致程序设计错误,因此在Java中不允许这样做。
// Java中不允许类似如下的操作
int k = 0;
{
int k = 1;
}
不受待见的goto语句
Java的设计者将goto作为保留字,但实际上并不能使用他。(所以在IDE中这个关键字会高亮显示,虽然他不能被使用,但变量名也不可以取为"goto")
尽管goto语句被认为是一种拙劣的程序设计风格,但是偶尔使用它跳出循环也是有益处的,特别是复杂的嵌套循环。
Java提供了一种带标签的break语句,其作用与goto语句很相似。
带标签的break语句
Java提供了一种带标签的break语句,用于跳出多重嵌套的循环语句
注意:标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号!
事实上,标签可以应用到任何“块”语句中,不过不提倡使用这种方式
下面的代码在输出 “3,0” 之后便紧跟着输出了“循环结束”
// 带标签的break语句,需要把标签放在代码块外,并紧贴代码块
target:
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
System.out.println(i + "," + j);
if (i == 3) {
// 直接跳出嵌套循环到target位置,并且不会再次执行该代码块里的内容
break target;
}
}
}
System.out.println("循环结束");
高精度的大数值类型
如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有用的类:BigInteger和BigDecimal
这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。
使用静态的valueOf方法可以将普通的数值转换为大数值
// 使用valueOf将普通的数值转换为大数值
BigInteger bigInteger = BigInteger.valueOf(100);
BigDecimal bigDecimal = BigDecimal.valueOf(2.2);
*对于大数值类型的计算,需要使用大数值类中的add和multiply等方法,不可以使用算术运算符(如:+ 和 )
System.out.println(bigInteger.multiply(BigInteger.valueOf(2))); // 200
普通浮点数和大数值浮点数在计算时的区别
// 浮点数运算的区别
System.out.println(bigDecimal.add(BigDecimal.valueOf(1.1))); // 3.3
System.out.println(2.2 + 1.1); // 3.3000000000000003
C++的运算符重载
与 C++不同,Java没有提供运算符重载功。程序员无法重定义 + 和 * 运算符,使其应用于BigInteger类的add和multiply运算。Java语言的设计者确实为字符串的连接重载了 + 运算符,但没有重载其他的运算符,也没有给Java程序员在自己的类中重载运算符的机会。
// Java字符串中的“+”运算符,也就是字符串拼接
System.out.println("Hello, " + "world!"); // Hello, world!
数组声明的不同形式
// Java风格的数组声明
int[] a;
// C风格的数组声明
int a[];
增强的for循环
Java有一种功能很强的循环结构,可以用来一次处理数组中的每个元素(其他类型的元素集合亦可)而不必为指定下标值而分心。
这种循环结构被称为:foreach循环 或 增强for循环
**语句格式为:for (variable : collection) {statement} **
注意:foreach中的变量只是可迭代对象中元素的引用,也就是暂存元素;所以不能通过修改foreach循环中变量的值来对实际可迭代对象中的元素产生影响。
定义一个变量用于暂存集合中的每一个元素,并执行相应的语句(当然,也可以是语句块)。
// 下面两个循环的输出结果相同
int[] array = {1,2,3};
for (int x: array){
System.out.println(x);
}
for (int i = 0; i < array.length; i++){
System.out.println(array[i]);
}
数组拷贝
在Java中,允许将一个数组变量拷贝给另一个数组变量。这时,两个变量将引用同一个数组(类似指针指向)。
如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用Arrays类的copyOf方法(只是值的复制)。
// 两个变量引用同一个数组, 也就是其中一个改变时, 另一个也改变
int[] a = {1,2,3};
int[] b = a;
b[0] = 100;
System.out.println(Arrays.toString(a)); // [999, 2, 3]
System.out.println(Arrays.toString(b)); // [999, 2, 3]
// 把a数组中的值赋值给b, 并指定b数组的长度, 其中一个改变时, 另一个不变
int[] a = {1,2,3};
int[] b = Arrays.copyOf(a, a.length);
b[0] = 100;
System.out.println(Arrays.toString(a)); // [1, 2, 3]
System.out.println(Arrays.toString(b)); // [999, 2, 3]
// 如果指定的长度小于原石数组的长度, 则只拷贝最前面的数据元素。
int[] c = Arrays.copyOf(a, a.length-1);
System.out.println(Arrays.toString(c)); // [1, 2]
在Java中,所有的数值类型所占据的字节数量与平台无关。
Java没有任何无符号(unsigned)形式的int、long、short或byte类型。
强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。最好将字符串作为抽象数据类型处理。
Java中的final关键字指示常量,表示这个变量只能被赋值一次。习惯上,常量名使用全大写。
整数被 0 除将会产生一个异常, 而浮点数被 0 除将会得到无穷大或 NaN 结果。
System.out.println(1/0); // java.lang.ArithmeticException: / by zero
System.out.println(1.0/0); // Infinity
**Java 没有内置的字符串类型, 而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String。**每个用双引号括起来的字符串都是String类的一个实例。
==字符串变量指向内存中相应的位置。如果复制一个字符串变量, 原始字符串与复制的字符串共享相同的字符。==总而言之,Java 的设计者认为共享带来的高效率远远胜过于提取、 拼接字符串所带来的低效率。
Java会自动进行垃圾回收,如果一块内存不再使用了,系统最终会将其回收。
不推荐使用switch语句,而用if … else if … else 代替,因为某个case分支语句下可能会忘记添加break语句,这种情况相当危险。
从 JavaSE 7开始,case标签还可以是字符串字面量。
创建一个数字数组时,所有元素都初始化为0。boolean数组的元素会初始化为false。对象数组的元素初始化为null,表示这些元素还未存放任何对象。
数组长度不可变,集合长度可变。
在Java应用程序的main方法中,程序名没有存储在args数组中。
// 命令行参数, 执行后args[0]是"-h", 而不是"Student"或"java"
java Student -h world
Math.random方法将返回一个0到1之间 (包含0、不包含1) 的随机浮点数。用n乘以这个浮点数,就可以得到从0到 n-1 之间的一个随机数。
// 获得一个从0到 n-1 之间的一个随机数r
int r = (int) (Math.random() * n);
Java实际上没有多维数组,只有一维数组。多维数组被解释为“数组的数组”。