用于基础类的构建器肯定在一个衍生类的构建器中调用,而且逐渐向上链接,使每个基础类使用的构建器都能得到调用。之所以要这样做,是由于构建器负有一项特殊任务:检查对象是否得到了正确的构建。
下面让我们看看一个例子,它展示了按构建顺序进行合成、继承以及多形性的效果:
class Meal
{
Meal() { System.out.println("Meal()"); }
}
class Bread
{
Bread() { System.out.println("Bread()"); }
}
class Cheese
{
Cheese() { System.out.println("Cheese()"); }
}
class Lettuce
{
Lettuce() { System.out.println("Lettuce()"); }
}
class Lunch extends Meal
{
Lunch() { System.out.println("Lunch()");}
}
class PortableLunch extends Lunch
{
PortableLunch() {
System.out.println("PortableLunch()");
}
}
class Sandwich extends PortableLunch
{
Bread b = new Bread();
Cheese c = new Cheese();
Lettuce l = new Lettuce();
Sandwich() {
System.out.println("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
}
输出结果如下:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
这意味着对于一个复杂的对象,构建器的调用遵照下面的顺序:
(1) 调用基础类构建器。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然
后是下一个衍生类,等等。直到抵达最深一层的衍生类。
(2) 按声明顺序调用成员初始化模块。
(3) 调用衍生构建器的主体。
构建器内部的多形性方法的行为
构建器应该构建当前类还是衍生类,这是一个问题,
如下例子:
abstract class Glyph
{
abstract void draw();
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph
{
int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println(
"RoundGlyph.RoundGlyph(), radius = "+ radius);
}
void draw() {
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors
{
public static void main(String[] args) {
new RoundGlyph(5);
}
}
输出结果:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
上文部分的初始化顺序并不十分完整,而那是解决问题的关键所在。初始化的实际过程是这样的:
(1) 在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。
(2) 就象前面叙述的那样,调用基础类构建器。此时,被覆盖的draw()方法会得到调用(的
确是在RoundGlyph构建器调用之前),此时会发现 radius的值为 0,这是由于步骤(1)
造成的。
(3) 按照原先声明的顺序调用成员初始化代码。
(4) 调用衍生类构建器的主体。
因此,设计构建器时一个特别有效的规则是:用尽可能简单的方法使对象进入就绪状态;如果可能,避免调用任何方法。在构建器内唯一能够安全调用的是在基础类中具有final 属性的那些方法(也适用于private方法,它们自动具有final 属性)。这些方法不能被覆盖,所以不会出现上述潜在的问题。
转载于:https://blog.51cto.com/zhenzhuangde/1727336