按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
197
…………………………………………………………Page 199……………………………………………………………
外,在第 13 章的后半部分,大家还会看到如何巧妙地利用内部类描述一个图形用户界面的行为。完成那里的
学习后,对内部类的认识将上升到一个前所未有的新高度。
7。7 构建器和多形性
同往常一样,构建器与其他种类的方法是有区别的。在涉及到多形性的问题后,这种方法依然成立。尽管构
建器并不具有多形性(即便可以使用一种“虚拟构建器”——将在第 11 章介绍),但仍然非常有必要理解构
建器如何在复杂的分级结构中以及随同多形性使用。这一理解将有助于大家避免陷入一些令人不快的纠纷。
7。7。1 构建器的调用顺序
构建器调用的顺序已在第4 章进行了简要说明,但那是在继承和多形性问题引入之前说的话。
用于基础类的构建器肯定在一个衍生类的构建器中调用,而且逐渐向上链接,使每个基础类使用的构建器都
能得到调用。之所以要这样做,是由于构建器负有一项特殊任务:检查对象是否得到了正确的构建。一个衍
生类只能访问它自己的成员,不能访问基础类的成员(这些成员通常都具有private 属性)。只有基础类的
构建器在初始化自己的元素时才知道正确的方法以及拥有适当的权限。所以,必须令所有构建器都得到调
用,否则整个对象的构建就可能不正确。那正是编译器为什么要强迫对衍生类的每个部分进行构建器调用的
原因。在衍生类的构建器主体中,若我们没有明确指定对一个基础类构建器的调用,它就会“默默”地调用
默认构建器。如果不存在默认构建器,编译器就会报告一个错误(若某个类没有构建器,编译器会自动组织
一个默认构建器)。
下面让我们看看一个例子,它展示了按构建顺序进行合成、继承以及多形性的效果:
//: Sandwich。java
// Order of constructor calls
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();
198
…………………………………………………………Page 200……………………………………………………………
Cheese c = new Cheese();
Lettuce l = new Lettuce();
Sandwich() {
System。out。println(〃Sandwich()〃);
}
public static void main(String'' args) {
new Sandwich();
}
} ///:~
这个例子在其他类的外部创建了一个复杂的类,而且每个类都有一个构建器对自己进行了宣布。其中最重要
的类是Sandwich ,它反映出了三个级别的继承(若将从Object 的默认继承算在内,就是四级)以及三个成
员对象。在 main()里创建了一个 Sandwich 对象后,输出结果如下:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
这意味着对于一个复杂的对象,构建器的调用遵照下面的顺序:
(1) 调用基础类构建器。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然后是下一个衍生
类,等等。直到抵达最深一层的衍生类。
(2) 按声明顺序调用成员初始化模块。
(3) 调用衍生构建器的主体。
构建器调用的顺序是非常重要的。进行继承时,我们知道关于基础类的一切,并且能访问基础类的任何
public 和 protected 成员。这意味着当我们在衍生类的时候,必须能假定基础类的所有成员都是有效的。采
用一种标准方法,构建行动已经进行,所以对象所有部分的成员均已得到构建。但在构建器内部,必须保证
使用的所有成员都已构建。为达到这个要求,唯一的办法就是首先调用基础类构建器。然后在进入衍生类构
建器以后,我们在基础类能够访问的所有成员都已得到初始化。此外,所有成员对象(亦即通过合成方法置
于类内的对象)在类内进行定义的时候(比如上例中的b,c 和 l),由于我们应尽可能地对它们进行初始
化,所以也应保证构建器内部的所有成员均为有效。若坚持按这一规则行事,会有助于我们确定所有基础类
成员以及当前对象的成员对象均已获得正确的初始化。但不幸的是,这种做法并不适用于所有情况,这将在
下一节具体说明。
7。7。2 继承和 finalize()
通过“合成”方法创建新类时,永远不必担心对那个类的成员对象的收尾工作。每个成员都是一个独立的对
象,所以会得到正常的垃圾收集以及收尾处理——无论它是不是不自己某个类一个成员。但在进行初始化的
时候,必须覆盖衍生类中的finalize()方法——如果已经设计了某个特殊的清除进程,要求它必须作为垃圾
收集的一部分进行。覆盖衍生类的 finalize()时,务必记住调用 finalize()的基础类版本。否则,基础类的
初始化根本不会发生。下面这个例子便是明证:
//: Frog。java
// Testing finalize with inheritance
class DoBaseFinalization {
public static boolean flag = false;
}
199
…………………………………………………………Page 201……………………………………………………………
class Characteristic {
String s;
Characteristic(String c) {
s = c;
System。out。println(
〃Creating Characteristic 〃 + s);
}
protected void finalize() {
System。out。println(
〃finalizing Characteristic 〃 + s);
}
}
class LivingCreature {
Characteristic p =
new Characteristic(〃is alive〃);
LivingCreature() {
System。out。println(〃LivingCreature()〃);
}
protected void finalize() {
System。out。println(
〃LivingCreature finalize〃);
// Call base…class version LAST!
if(DoBaseFinalization。flag)
try {
super。finalize();
} catch(Throwable t) {}
}
}
class Animal extends LivingCreature {
Characteristic p =
new Characteristic(〃has heart〃);
Animal() {
System。out。println(〃Animal()〃);
}
protected void finalize() {
System。out。println(〃Animal finalize〃);
if(DoBaseFinalization。flag)
try {
super。finalize();
} catch(Throwable t) {}
}
}
class Amphibian extends Animal {
Characteristic p =
new Characteristic(〃can live in water