天天看点

Java 解惑手册 读书笔记

一、符号谜题

1. 奇数性

错误答案:

public static boolean isOdd(int i){

return i % 2 == 1;

}

所有负数取余,次函数都会返回false.

正确答案:

public static boolean isOdd(int i){

return i % 2 != 0;

更好性能采用:

return (i & 1) != 0;

原因:

2. double位数打印问题

System.out.println(2.00 - 1.10);

将会打印0.8999999999999999 .

解决方案,如果位数较少就不使用double; 如果位数较多,使用BigDecimal.

3. public static void main(String args[]){

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,存在溢出问题。

乘法中都是以int进行计算,在赋值的时候才提升到long,这个时候已经发生溢出。

解决方案:

final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;

final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;

4. System.out.println(12345+5432l);

将打印17777,因为5432l的最后一个不是1,而是L。

5. System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));

将会打印cafebabe,而不是1cafebabe,混合类型计算导致。

如果十六进制和八进制字面常量的最高位被置位了,那么它们就是负数。在这个程序中,数字0xcafebabe是一个int 常量,它的最高位被置位了,所以它是一个负数。它等于十进制数值-889275714。

6. System.out.println((int)(char)(byte) -1);

将打印65535。符号扩展和零扩展的问题。

7. 三元操作符

char x = 'X';

int i = 0;

System.out.println(true ? x : 0);

System.out.println(false ? i : x);

将会打印88,

• 如果一个操作数的类型是T,T 表示byte、short 或char,而另一个操作数是一个int 类型的常量表达式,它的值是可以用类型T 表示的,那么条件表达式的类型就是T。

• 否则,将对操作数类型运用二进制数字提升,而条件表达式的类型就是第二个和第三个操作数被提升之后的类型。

8. 使用复合赋值时 如+= *= 注意隐式的类型转换。

short x = 0;

int i = 123456;

复合赋值编译将不会产生任何错误:

x += i; // 包含了一个隐藏的转型!

二、字符谜题

9.

System.out.print("H"+"a");

System.out.print('H'+'a');

第二个将会打印出169

10.

final String pig = "length: 10";

final String dog = "length: " + pig.length();

System.out. println("Animals are equal: "+ pig == dog);

将会打印false, 执行过程为:(("Animals are equal: "+ pig) == dog)

11.

System.out.println("a\u0022.length()+\u0022b".length());

编译器在将程序解析成各种符号之前,先将Unicode转义字符转换成为它们所表示的字符[JLS 3.2]。

13.

/**

* Generated by the IBM IDL-to-Java compiler, version 1.0

* from F:\TestRoot\apps\a1\units\include\PolicyHome.idl

* Wednesday, June 17, 1998 6:44:40 o’clock AM GMT+00:00

*/

注释 unicode编码转义问题。程序代码也存在这个问题

14. String(Byte[] )构造器不稳定, 使用时最好指定字符集 String(Byte[] , String charset )

15. replaceAll(String, String) 第一个参数为正则表达式,replaceAll(".", "\\")会替换每一个单字符,解决方案:

.replaceAll("\\." , "/") 或者 replaceAll(Pattern.quote("."),"/")

另外 String.replace(String, String) 的功能和replaceAll的功能一样,不同的是,全部按照字面值处理

16. 跨平台文件分隔符:File.separator ,

17. 在java代码中,

System.out.println('H'+'a');

http://www.baidu.com

System.out.print("2 + 2 = " + 2+2);

这种代码不会报错,因为”:“的作用。

18. 注意StringBuffer的构造器,没有针对char的constructor, 会强转为int,然后作为容量进行初始化的,可以使用针对String的构造函数。

一个char更像int而不是Stirng.

三、循环谜题

19. new Random().nextInt(int)等函数存在栅栏柱错误(fencepost error ),即达不到声明的长度,只能无限接近,但是在程序里,就会错1.

如果你要建造一个100 英尺长的栅栏,其栅栏柱间隔为10 英尺,那么你需要多少根栅栏柱呢?11根或9 根都是正确答案,这取决于是否要在栅栏的两端树立栅栏柱,但是10 根却是错误的 。

20. byte 与 int的比较 注意byte的范围,而且byte和int 的==操作,永远返回的都是false。需要强制转换的。

21. j = j++; 不会对j的值做任何更改。相当于

int tmp = j; j = j + 1; j = tmp;

22. 以下程序将会无限循环,当等于END时,再加1,END将变为Integer.MIN_VALUE。使用long,或者去掉=

int END = Integer.MAX_VALUE;

int START = END - 100;

int count = 0;

for (int i = START; i <= END; i++)

count++;

System.out.println(count);

23. 三个移位操作符:<<、>>和>>>。移位长度总是介于0 到31 之间,如果左操作数是long 类型的,则介于0 到63 之间。这个长度是对32取余的,如果左操作数是long 类型的,则对64 取余。如果试图对一个int 数值移位32 位,或者是对一个long 数值移位64 位,都只能返回这个数值自身的值。

右移操作符总是起到右移的作用,而左移操作符也总是起到左移的作用。负的移位长度通过只保留低5 位而剔除其他位的方式被转换成了正的移位长度——如果左操作数是long 类型的,则保留低6 位。因此,如果要将一个int数值左移,其移位长度为-1,那么移位的效果是它被左移了31 位。

移位长度是对32 取余的,或者如果左操作数是long 类型的,则对64 取余

2012年4月26日 23:42:58 29,P39

24. i != i的情况成立,当i为NaN的时候,double和float都有这个值。可以使用Double.NaN获得

25. 不使用浮点数 使得while (i != i + 0) {} 无限循环,——String

26. 请提供一个对i 的声明,将下面的循环转变为一个无限循环:

while (i != 0) {

i >>>= 1;

}

i为short byte等长度小于int的类型的任意负值可以实现。先转型为int值,使用符号拓展,在进行强转为窄类型会导致丢失信息。

复合赋值一定要注意类型问题

27. 如何使while (i <= j && j <= i && i != j) {} 无限循环下去:Integer i = new Integer(0); Integer j = new Integer(0);

前两个执行数值比较,第三个执行标识比较,so...

new Integer(0) == new Integer(0))为false。new Integer(0) == 0 为true.

28. 使 while (i != 0 && i == -i) {} 无限循环——int i = Integer.MIN_VALUE; 或者long i = Long.MIN_VALUE;

对于每一种有符号的整数类型(int、long、byte 和short),负的数值总是比正的数值多一个,这个多出来的值总是这种类型所能表示的最小数值。使用时小心溢出。

2012年4月27日 11:02:42 34 p45

29. 不要使用float作为循环索引,因为当float等浮点型,足够大时,+50不会对值不会产生影响

final int START = 2000000000;

for (float f = START; f < START + 50; f++)

count++;

System.out.println(count);

将立即结束,因为循环条件不成立。

50 的二进制表示只需要6 位,所以将50 加到2,000,000,000上不会对右边6 位之外的其他为产生影响。

从右边数过来的第7 位和第8 位仍旧是0。提升这个31 位的int 到具有24 位精度的float 会在第7 位和第8 位之间四舍五入,从而直接丢弃最右边的7 位

30. ms % 60*1000 == 0 不等同于 (ms % 60000 == 0) ——%和 *有相同的优先级

四。异常谜题

31. 下面的程序将返回false, 返回的true会被抛弃

static boolean decision() {

try {

return true;

} finally {

return false;

}

32. 第一个不能打印goodbye,第二个方式可以

try {

System.out.println("Hello world");

System.exit(0);

} finally {

System.out.println("Goodbye world");

}

//###############################

Runtime.getRuntime().addShutdownHook(

new Thread() {

public void run() {

System.out.println("Goodbye world");

});

System.exit(0);

33. 下面的程序会因为无限递归而进StackOverFlow,

public class Reluctant {

private Reluctant internalInstance = new Reluctant();

public Reluctant() throws Exception {

throw new Exception("I'm not coming out");

public static void main(String[] args) {

try {

Reluctant b = new Reluctant();

System.out.println("Surprise!");

} catch (Exception ex) {

System.out.println("I told you so");

}

34. 在关闭流的时候,会抛出异常,关闭操作一般在finally子句中执行,所以正确并且优雅的方案是:

} finally {

closeIgnoringException(in);

closeIgnoringEcception(out);

private static void closeIgnoringException(Closeable c) {

if (c != null) {

c.close();

} catch (IOException ex) {

// There is nothing we can do if close fails

35. 下面的函数接近无限循环(虽然最终会抛出stackoverflow异常),执行方式类似于先序遍历完全二叉树。

public static void main(String[] args) {

workHard();

System.out.println("It's nap time.");

private static void workHard() {

workHard();

五、类谜题

六、库谜题

36. 千万不要在一个整型字面常量的前面加上一个0;这会使它变成一个八进制字面常量

37. 不要因为偶然地添加了一个返回类型,而将一个构造器声明变成了一个方法声明

38. Math.abs() ——如果其参数等于Integer.MIN_VALUE等MIN_VALUE,那么产生的结果与该参数相同 ,也是负值

2012年4月29日 19:09:34 p100

39. 下面是一个经常使用到的比较器,但是该比较器存在一个很大的问题,溢出问题。

public int compare(Integer i1, Integer i2) {

return i2 - i1;

下面的比较器可以正常工作。

public int compare(Integer i1, Integer i2) {

return (i2 < i1 ? -1 : (i2 == i1 ? 0 :1));

Java谜题7——更多的类谜题

40. 避免重用java包内自带的类名,会带来意想不到的问题,比如:

public class StrungOut {

public static void main(String[] args) {

String s = new String("Hello world");

System.out.println(s);

class String {

private final java.lang.String s;

public String(java.lang.String s) {

this.s = s;

public java.lang.String toString() {

return s;

上面的程序,会因为main方法的参数不对而报找不到main方法的错误。

41. 静态导入import static 。。。对可读性有很大的影响,而且遮蔽可能会导致问题,尽量少用。

不定参数也存在同样问题

42. 重用变量名不是一个好主意,尽量选择方法覆盖的方式实现。另外,final 修饰符不影响重用该变量名。

重用名字是危险的;应该避免隐藏、遮蔽和遮掩 。

Java谜题7——更多的库谜题

2012年5月2日 23:09:22 P129

43. 反射相关的东西就不是很清楚了,Method.invoke()感觉功能很强大....

44. Process类拥有一个waitFor方法,返回值为执行的状态值,执行过程是阻塞式的。

但是有一点需要注意:

由于某些本地平台只提供有限大小的缓冲,所以如果未能迅速地读取子进程(subprocess)的输出流,就有可能会导致子进程的阻塞,甚至是死锁

故在调用之前需要排空输出流:

// Master

Process process = Runtime.getRuntime().exec(COMMAND);

int exitValue = process.waitFor();

System.out.println("exit value = " + exitValue);

改为:

Process process = Runtime.getRuntime().exec(COMMAND);

drainInBackground(process.getInputStream());

int exitValue = process.waitFor();

调用的排空输出流的方法为:

static void drainInBackground(final InputStream is) {

new Thread(new Runnable(){

public void run(){

try{

while( is.read() >= 0 );

} catch(IOException e){

// return on IOException

}).start();

2012年5月2日 23:42:31 p138

Java谜题9——高级谜题

45. 常量变量将会被编译进那些引用它们的类中。一个常量变量就是任何被常

量表达式初始化的原始类型或字符串变量。令人惊讶的是,null 不是一个常量

表达式。

46.