Java的动态绑定是与C++不同的一大特点。这篇文章主要记录看Java编程思想关于继承中的多态的详细讲解部分的一些细节,并由此给出了一条编程准则。
继承中构造器的调用顺序为:首先调用所有基类的构造器(从最上层的基类开始),然后按声明顺序调用成员的初始化方法,最后调用本类的构造器(C++的构造函数调用顺序也一样)。可以用下面的代码测试:
class Meal {
Meal() {
System.out.println("Meal");
}
}
class Bread {
Bread() {
System.out.println("Bread");
}
}
class Milk {
Milk() {
System.out.println("Milk");
}
}
class Breakfast extends Meal {
Breakfast() {
System.out.println("Breakfast");
}
}
public class Sandwich extends Breakfast {
private Bread b = new Bread();
private Milk m = new Milk();
public Sandwich() {
System.out.println("Sandwich");
}
public static void main(String[] args) {
new Sandwich();
}
}
/*输出为
Meal
Breakfast
Bread
Milk
Sandwich*/
如果要调用构造器内部一个动态绑定的方法,就要用到该方法覆写后的方法,而覆写后的方法可能需要子类成功初始化后才能正常被调用。由于构造器的顺序是先调用基类的构造器,因此可能会出现预期之外的运行错误。如下面的代码:
class Graphic {
void draw() {
System.out.println("Graphic draw()");
}
Graphic() {
System.out.println("Graphic() before draw()");
draw();
System.out.println("Graphic() after draw()");
}
}
class Circle extends Graphic {
private int radius = 1;
Circle(int r) {
radius = r;
System.out.println("Circle(), radius = " + radius);
}
void draw() {
System.out.println("Circle(), radius = " + radius);
}
}
public class Test {
public static void main(String[] args) {
new Circle(5);
}
}
/*输出为
Graphic() before draw()
Circle(), radius = 0
Graphic() after draw()
//前三行调用的是基类的构造器,但第二行语句draw()调用的是动态绑定的子类的覆写后的方法,而此时子类还没有初始化,因此半径输出是错误的。
Circle(), radius = 5
*/
因此前面所说的构造器的调用顺序其实是不完全的,事实上在任何调用之前,构造器会首先将分配给对象的存储空间初始化为二进制的0,再执行上述顺序。这就保证了没有初始化的任何数据都是0。如果是C++,那么在处理这种情况时会更加合理,它的输出结果为:
/*Graphic() before draw()
Graphic draw()
Graphic() after draw()
Circle(), radius = 5*/
总之,在Java中编写构造器时有一条准则:用尽可能简单的方法使对象进入正常状态。如果可以,避免使用其他方法。