[Java解惑]读书笔记分享
这本书主要讲了Java中经常遇到的坑, 并给出了解释以及解决办法. 读完之后很有收获
- 读书笔记
-
表达式之谜
-
奇数性
用下面的表达式判断一个数是否是奇数
public static boolean isOdd(int i) { return i % 2 == 1; }
问题: 负数无法得出正确的结果. 例如当
i = -3
时, 上述结果是-1
.可以修改为如下形式
public static boolean isOdd(int i) { return i % 2 != 0; }
更进一步的, 可以考虑把区域操作改为
AND(&)
.public static boolean isOdd(int i) { return (i & 1) != 0; }
-
找0时刻
计算表达式
System.out.println(2.00 - 1.10); // 0.8999999999999999
原因: 并非所有的小数都可以用二进制浮点数明确表示
请注意, 虽然表达式
System.*out*.printf("%.2f", (2.00 - 1.10));
会显示出正确的结果, 但是这并不表示是对底层通用的解决方案.因为二进制浮点数对于货币运算是非常不合适的.
一种方案是以分为单位, 另外一种是使用
BigDecimal
.BigDecimal bigDecimalA = new BigDecimal("2.0"); BigDecimal bigDecimalB = new BigDecimal("1.1"); System.out.println(bigDecimalA.min(bigDecimalB));
一定要使用字符串构造参数
BigDecima(String)
, 不要使用BigDecima(double)
.BigDecimal bigDecimal = new BigDecimal(.1); System.out.println(bigDecimal); // 0.1000000000000000055511151231257827021181583404541015625
-
长整除
两个long类型的数相除.
final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000; final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000; System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
运行结果是5
MICROS_PER_DAY
溢出了. 虽然放在long
中是可以的, 但是放在int
中会溢出.这个计算是按照
int
来计算的, 只有在运算完之后, 才会被提升到long
, 而此时已经溢出了.为什么会按照
int
的方式计算? 因为所有的因子都是int
类型.一种比较好的方式是把
long
类型作为第一个因子.修改后的程序如下
final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000; final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000; System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
-
初级问题
计算表达式
System.out.println(12345 + 5432l); // 17777
为什么会这样呢?
5432l
中的l
, 表示该数字是long
类型. 因此建议写成大写. -
十六进制的趣事
计算下面表达式
System.out.println(Long.toHexString(0x100000000L + 0xcafebabe)); // cafebabe
为什么不是
1cafebabe
呢要注意到, 十六进制不像十进制一样字面量都是正的, 如果想表示负数在前面加
-
号就可以了.System.out.println(0xcafebabe); // -889275714 System.out.println(0x1cafebabeL); // 7700658878
因此, 该表达式左侧是一个long类型数字, 右侧是一个
int
类型的数字, 且为负数. 为了执行该运算, 会将int
类型转化为long
类型. 提升之后的数字是0xffffffffcafebabe
.修改方法如下. 即把两个数字都变成
long
类型.System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));
-
多重转型
观察下面表达式
System.out.println((int)(char)(byte)-1);
其结果不是
-1
, 而是65535
转化顺序是
int → byte → char → int
该程序的行为紧密依赖于转型的符号扩展行为. Java使用基于2的补码的二进制运算, 因此int类型的数值-1的所有32位都是置位的.
第一次从int → byte, 窄化转换, 仍是-1.
第二次byte → char, 拓展并窄化的转化. byte是有符号类型的, char是无符号类型的. 不能用char表示一个负的byte. 因此这个过程是byte → int → char.
可以将规则描述如下: 如果最初的数值类型是有符号的,那么就执行符号扩展。如果他是char那么不管他将要被转化成什么类型,都执行零扩展。
-
互换内容
int x = 1984; int y = 2001; x^= y^= x^= y; System.out.println("x = " + x + " y = " + y); // x = 0 y = 1984
一种可行的方式如下
int x = 1984; int y = 2001; x = x ^ y; y = y ^ x; x = x ^ y; System.out.println("x = " + x + " y = " + y); // x = 2001 y = 1984
这种方式不推荐使用.
Java的操作符操作是从左到右执行的.
给我们的经验就是不要对相同的变量赋值两次.
-
Dos Equis
观察下面表达式
char x = 'X'; int i = 0; System.out.println(true ? x : 0); System.out.println(false ? i : x); // X // 88
转化规则如下
最好在条件表达式中使用相同的类型.
-
半斤
不能简单的认为下面两个表达式是相等的
x += i; x = x + 1;
原因如下
可以观察下面的例子
short x = 0; int i = 123456; x += i; System.out.println(x); // -7616
因此, 不要将符合运算符用于byte, short, char类型. 避免符合类型的转型.
-
八两
但是, 上面的例子反过来, 也不能就认为高枕无忧了.
复合赋值操作符要求两个操作数都是原生类型的,例如int。或包装了的原生类型。例如integer. 但是有一个例外, 那就是如果操作符左侧的操作数是string类型的。那么他允许右侧的操作数是任意类型。
-
-
字符之谜
- 最后的笑声
- ABC
- 动物庄园
- 转义字符的溃败
- 令人晕头转向的Hello
- 行打印程序
- 嗯?
- 字符串奶酪
- 漂亮的火花
- 我的类是什么
- 我的类是什么? 镜头2
- URL的愚弄
- 不劳无获
-
循环之谜
-
异常之迷
-
类之迷
-
库之迷
-
更多类之谜
-
更多库之迷
-
高级谜题
-