下面代码的输出是什么?
importjava.util.ArrayList;
public class InitializationTip {
public static void main(String args[ ]) {
new B();
}
publicInitializationTip() {
; foo();
}
protected void foo() {
// do some init work here
}
}
class B extends InitializationTip {
privateArrayList list = new ArrayList();
public B() {
foo();
}
; protected void foo() {
; System.out.println(list);
}
}
正确答案是null,而且其解释还违反常规。是这样的,当B的实例创建时,它的构造方法被调用,然后其父类的无参数的构造方法也被调用。规则是如果一个特定的父类的构造方法没有在子类的构造方法中被调用,那么编译器会插入一个父类的默认的构造方法。你可以通过查看反编译的字节码中看到这一情况。
Method B()
0 aload_01 invokespecial #1 <Method InitializationTip()>
4 aload_0
5 new #2 <Class java.util.ArrayList>
8 dup
9 invokespecial #3 <Method java.util.ArrayList()>
12 putfield #4 <Field java.util.ArrayList list>
15 aload_0
16 invokevirtual #5 <Method void foo()>
19 return
在B的构造方法的第一行会发出对一个InitializationTip的构造方法的调用。这个调用在对list赋值之前发生。在InitializationTip的构造方法调用一个保护成员函数foo()时会出现问题。由于foo()是protected类型的,因此它能够被子类B重载,事实上也被B重载。因为B中的重载方法foo()访问实例变量list,而list已经由构造方法赋值,所以结果输出是null。在程序执行退出了父类的构造方法之后,list会被赋值,foo()被B的构造方法调用,然后所有的如预期的发生。
教训是什么?不要在构造方法中调用可重载的方法。你不知道重载类在那个重载方法会做些什么。在这种情况下,不但会出现很多错误,而且由这类程序产生的Bug很微妙,很难调试出来。
遵守这个规则,在在构造方法中调用可重载方法可以节省很多时间和精力。