构造器的调用顺序

来源:互联网 发布:js正则大于等于0整数 编辑:程序博客网 时间:2024/04/28 03:52
构造器的调用顺序已在第 4 章进行了简要说明,并在第 6 章再次提到,但那些都是在多态
引入之前介绍的。


基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每
个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊任务:检查对
象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通
常是 private 类型)。只有基类的构造器才具有恰当的知识和权限对自己的元素进行初始化。
因此,必须令所有构造器都得到调用,否则所有对象就不可能被正确构造。这正是编译器为
什么要强制每个导出类部分都必须调用构造器的原因。在导出类的构造器主体中,如果我们
没有明确指定调用某个基类构造器,它就会“默默”地调用缺省构造器。如果不存在缺省构造
器,编译器就会报错(若某个类没有构造器,编译器会自动合成出一个缺省构造器)。


让我们来看下面这个例子,它展示了组合、继承以及多态的在构建顺序上的效果:


//: c07:Sandwich.java
// Order of constructor calls.
package c07; 
import com.bruceeckel.simpletest.*; 


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()");}
}
 










public class Sandwichextends PortableLunch { 
private static Test monitor = new Test(); 
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce(); 
public Sandwich() {
    System.out.println("Sandwich()");
  }
public static void main(String[] args) { 
new Sandwich(); 
    monitor.expect(new String[] { 
"Meal()",
"Lunch()",
"PortableLunch()",
"Bread()",
"Cheese()",
"Lettuce()",
"Sandwich()"
    });
  }
} ///:~






在这个例子中,用其他类创建了一个复杂的类,而且每个类都有一个它声明自己的构造器。
其中最重要的类是 Sandwich,它反映出了三层级别的继承(若将从 Object 的隐含继承也
算在内,就是四级)以及三个成员对象。当在 main()里创建一个 Sandwich 对象后,我们
就可以看到输出结果。这也表明了这一复杂对象调用构造器要遵照下面的顺序:


1. 调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,
然后是下一层导出类,等等。直到最低层的导出类。
2. 按声明顺序调用成员的初始状态设置模块。
3. 调用导出类构造器的主体。


构造器的调用顺序是很重要的。当进行继承时,我们已经知道基类的一切,并且可以访问基
类中任何声明为 public 和 protected 的成员。这意味着在导出类中,必须假定基类的所有
成员都是有效的。一种标准方法是,构造动作一经发生,那么对象所有部分的全体成员都会
得到构建。然而,在构造器内部,我们必须确保所要使用的成员都已经构建完毕。为确保这
一目的,唯一的办法就是首先调用基类构造器。那么在进入导出类构造器时,在基类中可供
我们访问的成员都已得到初始化。此外,在构造器中的所有成员必须有效也是因为当成员对
象在类内进行定义的时候(比如上例中的 b,c 和 l),我们应尽可能地对它们进行初始化
(也就是,通过组合方法将对象置于类内)。若遵循这一规则,那么我们就能确定所有基类
成员以及当前对象的成员对象都已初始化。但遗憾的是,这种做法并不适用于所有情况,我

们会在下一节看到。


原创粉丝点击