按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
然,在一些特殊的场合,我们仍然必须作出一些调整,但这并不是一个好的做法。
注意Cleanser 在它的接口中含有一系列方法:append(),dilute(),apply(),scrub()以及print()。由于
Detergent 是从Cleanser 衍生出来的(通过 extends 关键字),所以它会自动获得接口内的所有这些方法—
—即使我们在 Detergent 里并未看到对它们的明确定义。这样一来,就可将继承想象成“对接口的重复利
用”或者“接口的再生”(以后的实施细节可以自由设置,但那并非我们强调的重点)。
正如在 scrub()里看到的那样,可以获得在基础类里定义的一个方法,并对其进行修改。在这种情况下,我
们通常想在新版本里调用来自基础类的方法。但在 scrub()里,不可只是简单地发出对scrub()的调用。那样
便造成了递归调用,我们不愿看到这一情况。为解决这个问题,Java 提供了一个 super 关键字,它引用当前
类已从中继承的一个“超类”(Superclass)。所以表达式super。scrub()调用的是方法 scrub()的基础类版
本。
进行继承时,我们并不限于只能使用基础类的方法。亦可在衍生出来的类里加入自己的新方法。这时采取的
做法与在普通类里添加其他任何方法是完全一样的:只需简单地定义它即可。extends 关键字提醒我们准备
将新方法加入基础类的接口里,对其进行“扩展”。foam()便是这种做法的一个产物。
在Detergent。main()里,我们可看到对于Detergent 对象,可调用Cleanser 以及Detergent 内所有可用的
方法(如foam())。
6。2。1 初始化基础类
由于这儿涉及到两个类——基础类及衍生类,而不再是以前的一个,所以在想象衍生类的结果对象时,可能
会产生一些迷惑。从外部看,似乎新类拥有与基础类相同的接口,而且可包含一些额外的方法和字段。但继
承并非仅仅简单地复制基础类的接口了事。创建衍生类的一个对象时,它在其中包含了基础类的一个“子对
象”。这个子对象就象我们根据基础类本身创建了它的一个对象。从外部看,基础类的子对象已封装到衍生
类的对象里了。
当然,基础类子对象应该正确地初始化,而且只有一种方法能保证这一点:在构建器中执行初始化,通过调
用基础类构建器,后者有足够的能力和权限来执行对基础类的初始化。在衍生类的构建器中,Java 会自动插
入对基础类构建器的调用。下面这个例子向大家展示了对这种三级继承的应用:
//: Cartoon。java
// Constructor calls during inheritance
class Art {
Art() {
System。out。println(〃Art constructor〃);
}
}
class Drawing extends Art {
Drawing() {
System。out。println(〃Drawing constructor〃);
}
}
public class Cartoon extends Drawing {
Cartoon() {
System。out。println(〃Cartoon constructor〃);
}
public static void main(String'' args) {
Cartoon x = new Cartoon();
143
…………………………………………………………Page 145……………………………………………………………
}
} ///:~
该程序的输出显示了自动调用:
Art constructor
Drawing constructor
Cartoon constructor
可以看出,构建是在基础类的“外部”进行的,所以基础类会在衍生类访问它之前得到正确的初始化。
即使没有为 Cartoon()创建一个构建器,编译器也会为我们自动合成一个默认构建器,并发出对基础类构建
器的调用。
1。 含有自变量的构建器
上述例子有自己默认的构建器;也就是说,它们不含任何自变量。编译器可以很容易地调用它们,因为不存
在具体传递什么自变量的问题。如果类没有默认的自变量,或者想调用含有一个自变量的某个基础类构建
器,必须明确地编写对基础类的调用代码。这是用 super 关键字以及适当的自变量列表实现的,如下所示:
//: Chess。java
// Inheritance; constructors and arguments
class Game {
Game(int i) {
System。out。println(〃Game constructor〃);
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
System。out。println(〃BoardGame constructor〃);
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
System。out。println(〃Chess constructor〃);
}
public static void main(String'' args) {
Chess x = new Chess();
}
} ///:~
如果不调用 BoardGames()内的基础类构建器,编译器就会报告自己找不到Games()形式的一个构建器。除此
以外,在衍生类构建器中,对基础类构建器的调用是必须做的第一件事情(如操作失当,编译器会向我们指
出)。
2。 捕获基本构建器的违例
正如刚才指出的那样,编译器会强迫我们在衍生类构建器的主体中首先设置对基础类构建器的调用。这意味
着在它之前不能出现任何东西。正如大家在第 9 章会看到的那样,这同时也会防止衍生类构建器捕获来自一
个基础类的任何违例事件。显然,这有时会为我们造成不便。
144
…………………………………………………………Page 146……………………………………………………………
6。3 合成与继承的 结合
许多时候都要求将合成与继承两种技术结合起来使用。下面这个例子展示了如何同时采用继承与合成技术,
从而创建一个更复杂的类,同时进行必要的构建器初始化工作:
//: PlaceSetting。java
// bining position & inheritance
class Plate {
Plate(int i) {
System。out。println(〃Plate constructor〃);
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
System。out。println(
〃DinnerPlate constructor〃);
}
}
class Utensil {
Utensil(int i) {
System。out。println(〃Utensil constructor〃);
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
System。out。println(〃Spoon constructor〃);
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
System。out。println(〃Fork constructor〃);
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
System。out。println(〃Knife constructor〃);
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
System。out。println(〃Custom constructor〃);
145
…………………………………………………………Page 147……………………………………………………………
}
}
public class PlaceSetting extends Custom {
Spoon sp;
Fork frk;
Knife kn;
DinnerPlate pl;
PlaceSetting(int i) {
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
System。out。println(
〃PlaceSetting constructor〃);
}
public static void main(String'' args) {
PlaceSetting x = new PlaceSetting(9);
}
} ///:~
尽管编译器会强迫我们对基础类进行初始化,并要求我们在构建器最开头做这一工作,但它并不会监视我们
是否正确初始化了成员对象。所以对此必须特别加以留意。
6。3。1 确保正确的清除
Java 不具备象C++的“破坏器”那样的概念。在 C++中,一旦破坏(清除)一个对象,就会自动调用破坏器
方法。之所以将其省略,大概是由于在Java 中只需简单地忘记对象,不需强行破坏它们。垃圾收集器会在必
要的时候自动回收内存。
垃圾收集器大多数时候都能很好地工作,但在某些情况下,我们的类可能在自己的存在时期采取一些行动,
而这些行动要求必须进行明确的清除工作。正如第 4 章已经指出的那样,我们并不知道垃圾收集器什么时候
才会显身,或者说不知它何时会调用。所以一旦希望为一个类清除什么东西,必须写一个特别的方法,明
确、专门地来做这件事情。同时,还要让客户程序员知道他们必须调用这个方法。而在所有这一切的后面,
就如第 9 章(违例控制)要详细解释的那样,必须将这样的清除代码置于一个 finally 从句中,从而防范任
何可能出现的违例事件。
下面介绍的是一个计算机辅助设计系统的例子,它能在屏幕上描绘图形:
//: CADSystem。java
// Ensuring proper cleanup
import java。util。*;
class Shape {
Shape(int i) {
System。out。println(〃Shape constructor〃);
}
void cleanup() {
System。out。println(〃Shape cleanup〃);
}
}
class Circle extends Shape {
Circle(int i) {
146
…………………………………………………………Page 148……………………………………………………………
super(i);
System。out。println(〃Drawing a Circle〃);
}
void cleanup() {
System。out。println(〃Erasing a Circle〃);
super。cleanup();
}
}
class Tri