[Java解惑]读书笔记分享

这本书主要讲了Java中经常遇到的坑, 并给出了解释以及解决办法. 读完之后很有收获

[Java解惑]读书笔记分享_jvm

  • 读书笔记
  • 表达式之谜
  • 奇数性
    用下面的表达式判断一个数是否是奇数
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的愚弄
  • 不劳无获
  • 循环之谜
  • 异常之迷
  • 类之迷
  • 库之迷
  • 更多类之谜
  • 更多库之迷
  • 高级谜题