WsztRush

Java 解惑

这本书给我们列举了很多Java编程中容易产生迷惑的地方,先看几个例子:

1、在刚开始学编程的时候都会遇到swap操作,开始作为一个新手老老实实地用另外一个变量tmp来存,偶然看到用异或来实现感觉好牛逼,不过这种方式在Java上行不通:

int x = 1;
int y = 2;
x ^= y ^= x ^= y;
System.out.println("x = " + x + " y = " + y);

出乎意料地是这段代码执行的结果是x = 0 y = 1,而不是希望的x = 2 y = 1,接下来从字节码(javap -c)中找答案:

   0:   iconst_1
   1:   istore_1
   2:   iconst_2
   3:   istore_2
   4:   iload_1
   5:   iload_2
   6:   iload_1
   7:   iload_2
   8:   ixor
   9:   dup
   10:  istore_1
   11:  ixor
   12:  dup
   13:  istore_2
   14:  ixor
   15:  istore_1

原来执行过程竟然是这样的,为了更清晰给一个图来看栈帧的变化:

栈帧的变化

在Java语言规范描述中:操作符的操作数是从左到右求值的,对于x^=expr的表达式,x的值是在计算expr之前被提取的,那么因此也就有了上面这个结果。

2、在做ACM的时候经常会用到位操作,但是被下面这个代码还是惊了一下:

int i = 0;
while (-1 << i != 0)
    i++;
System.out.println(i);// 没输出,死循环啦。

对于-1直观的想法是在i=32的时候-1<<i==0,因为左移了32位嘛,随便什么数都成0了。

原因很简单:对于太大的数位操作会取模,比如int来说移位数为i&31,对于long来说移位数为i&63

3、对于初始化顺序大家应该都是知道的,但是当看到下面这段代码输出9900的时候还是得仔细看一下:

public class TestMain {
    static {
        initializeIfNecessary();
    }
    private static int sum;

    public static int getSum() {
        initializeIfNecessary();
        return sum;
    }

    private static boolean initialized = false;// 这里会设置成false。

    private static synchronized void initializeIfNecessary() {
        if (!initialized) {
            for (int i = 0; i < 100; i++)
                sum += i;
            initialized = true;
        }
    }

    public static void main(String[] args) {
        System.out.println(TestMain.getSum());
    }
}

在加载的时候会先分配内存,然后依次执行static,而其顺序和申明的顺序一致,那么这个结果自然就明白了。

4、第一眼看去这段代码重写的equals,而且非常正确,但是输出却是false

public class Name {
	private String first, last;
	public Name(String first, String last) {
		this.first = first;
		this.last = last;
	}
	public boolean equals(Object o) {
		if (!(o instanceof Name))
			return false;
		Name n = (Name)o;
		return n.first.equals(first) && n.last.equals(last);
	}
	public static void main(String[] args) {
		Set s = new HashSet();
		s.add(new Name("Mickey", "Mouse"));
		System.out.println(
		s.contains(new Name("Mickey", "Mouse")));
	}
}

这就是没有仔细思考的结果,想一下HashSet.contains()的运行机制就会焕然大悟:肯定是先比较hashCode,相同的情况下才调用equals。所以:无论何时,只要你覆盖了equals方法,就同时必须覆盖hashCode方法

5、对于这段代码可能会直观的顺着代码写的顺序去执行,但是非常容易忽略掉一点static是类初始化的一部分,当执行到t.join()的时候貌似主线程在等待t执行完成,但是此时主线程也在等待自己执行完成,所以死锁了。。。

public class Lazy {
	private static boolean initialized = false;
	static {
	Thread t = new Thread(new Runnable() {
		public void run() {
			initialized = true;
		}
	});
	t.start();
		try{
			t.join();
		}catch (InterruptedException e){
			throw new AssertionError(e);
		}
	}
	public static void main(String[] args){
		System.out.println(initialized);
	}
}

当然书中的例子并不只上面几个(一共有95个),总体来看有:

总结

不可否认看完这本书的很多例子能帮助我们写出更高质量的代码(比如覆写equels不覆写hashCode),不过很多例子(尤其是继承)编写很多年的工程代码都不会遇到。

看完这本书最大的收获就是对Java有了更深的理解。另外,像位移这种反直觉的设计真的好吗?