最近闲着的时候在复习《Thinking in Java》,昨天看到了多态这一章,想说说关于Java的构造方法和类的初始化问题。
先来看一个简单的程序:
public
class A extends F5 { private F1 f1 = new F1(); private F2 f2 = new F2();A() {
System.
out.println("A");}
public static void main(String args[]) {A a =
new A();}
}
class
F1 {F1() {
System.
out.println("F1");}
}
class
F2 {F2() {
System.
out.println("F2");}
}
class
F3 {F3() {
System.
out.println("F3");}
}
class
F4 extends F3 {F4() {
System.
out.println("F4");}
}
class
F5 extends F4 {F5() {
System.
out.println("F5");}
}
结果是:
F3
F4
F5
F1
F2
A
这段代码是怎么执行的呢?首先,F1和F2 是两个单独的类,与其它的类并没有继承关系。从F3类开始,后一个类是前一个类的子类,直到类A。也就是说,按照子类指向父类的顺序,我们可以看出:
F3<---F4<---F5<---A
每个类都定义了自己的构造方法,即简单打印出该类的名称。
当程序开始运行的时候,main()方法是入口。从main()方法进入后,我们可以看到就只有一条语句,即:
A a = new A();
这句话创建了一个A类的对象。现在开始是重点了。我们知道,当创建一个对象的时候,对象所属类的构造方法将被调用。但是如果这个类是某个类的子类,也就是说,该对象的类有父类的时候,会先去调用父类的构造方法。如果父类还有父类的话,那么就又会上一层调用父类的父类的构造方法,直到最顶层的父类为止(这里Object类不参与讨论,因为这是所有Java类的父类,已经隐藏实现了的),然后再一层一层向下调用子类的构造方法。
为什么会先调用父类的构造方法?因为在继承中,子类可以访问父类的public和protected成员。但是这些成员要确保存在才能被子类所访问。因此,创建一个子类的对象的时候,在子类的构造方法里面,必须要确保这些成员被创建,唯一的方法就是先调用父类的构造方法来确保这些成员会被正确创建。这样,进入子类的构造方法的时候,父类的这些成员都已被初始化,能够被我们访问到。
现在我们知道为什么会先调用最顶层的父类的调用方法了。回到上面的程序。当执行new A()的时候,先调用最顶层父类F3的构造方法,然后F4,然后F5。也许有人会问在结果中,F5之后为什么不是A而是F1和F2,然后再是A呢?这里是另一个要注意的,即:当调用完所有父类的构造方法时,将会先初始化子类的成员变量,然后再执行子类的构造方法。A中有两个成员变量,f1和f2,它们分别是F1和F2的对象。因此,在调用A的构造方法前,会先创建f1和f2,然后再执行A的构造方法,所以,我们会在结果中看到F5之后是F1,F2,最后才是A。
至此,我们可以明白构造方法的调用顺序了:
1.有父类的话先调用父类的构造方法,如果父类之上还有父类,则继续向上调用,直到最顶层父类为止,再一层一层向下调用其它父类的构造方法;
2.当调用完最靠近子类的父类的构造方法时,初始化子类的成员变量;
3.执行子类的构造方法。