友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第31部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
//: 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 Triangle extends Shape {
Triangle(int i) {
super(i);
System。out。println(〃Drawing a Triangle〃);
}
void cleanup() {
System。out。println(〃Erasing a Triangle〃);
super。cleanup();
}
}
class Line extends Shape {
private int start; end;
Line(int start; int end) {
super(start);
this。start = start;
this。end = end;
System。out。println(〃Drawing a Line: 〃 +
start + 〃; 〃 + end);
}
void cleanup() {
System。out。println(〃Erasing a Line: 〃 +
start + 〃; 〃 + end);
super。cleanup();
}
}
public class CADSystem extends Shape {
private Circle c;
private Triangle t;
private Line'' lines = new Line'10';
CADSystem(int i) {
super(i + 1);
for(int j = 0; j 《 10; j++)
lines'j' = new Line(j; j*j);
c = new Circle(1);
t = new Triangle(1);
System。out。println (〃bined constructor〃);
}
void cleanup() {
System。out。println(〃CADSystem。cleanup()〃);
t。cleanup();
c。cleanup();
147
…………………………………………………………Page 149……………………………………………………………
for(int i = 0; i 《 lines。length; i++)
lines'i'。cleanup();
super。cleanup();
}
public static void main(String'' args) {
CADSystem x = new CADSystem(47);
try {
// Code and exception handling。。。
} finally {
x。cleanup();
}
}
} ///:~
这个系统中的所有东西都属于某种 Shape (几何形状)。Shape 本身是一种 Object (对象),因为它是从根
类明确继承的。每个类都重新定义了Shape 的cleanup()方法,同时还要用 super 调用那个方法的基础类版
本。尽管对象存在期间调用的所有方法都可负责做一些要求清除的工作,但对于特定的Shape 类——Circle
(圆)、Triangle (三角形)以及Line (直线),它们都拥有自己的构建器,能完成“作图”(draw )任
务。每个类都有它们自己的cleanup()方法,用于将非内存的东西恢复回对象存在之前的景象。
在main()中,可看到两个新关键字:try 和 finally。我们要到第9 章才会向大家正式引荐它们。其中,try
关键字指出后面跟随的块(由花括号定界)是一个“警戒区”。也就是说,它会受到特别的待遇。其中一种
待遇就是:该警戒区后面跟随的finally 从句的代码肯定会得以执行——不管try 块到底存不存在(通过违
例控制技术,try 块可有多种不寻常的应用)。在这里,finally 从句的意思是“总是为 x 调用cleanup(),
无论会发生什么事情”。这些关键字将在第9 章进行全面、完整的解释。
在自己的清除方法中,必须注意对基础类以及成员对象清除方法的调用顺序——假若一个子对象要以另一个
为基础。通常,应采取与C++编译器对它的“破坏器”采取的同样的形式:首先完成与类有关的所有特殊工
作(可能要求基础类元素仍然可见),然后调用基础类清除方法,就象这儿演示的那样。
许多情况下,清除可能并不是个问题;只需让垃圾收集器尽它的职责即可。但一旦必须由自己明确清除,就
必须特别谨慎,并要求周全的考虑。
1。 垃圾收集的顺序
不能指望自己能确切知道何时会开始垃圾收集。垃圾收集器可能永远不会得到调用。即使得到调用,它也可
能以自己愿意的任何顺序回收对象。除此以外,Java 1。0 实现的垃圾收集器机制通常不会调用 finalize()方
法。除内存的回收以外,其他任何东西都最好不要依赖垃圾收集器进行回收。若想明确地清除什么,请制作
自己的清除方法,而且不要依赖finalize()。然而正如以前指出的那样,可强迫Java1。1 调用所有收尾模块
(Finalizer)。
6。3。2 名字的隐藏
只有C++程序员可能才会惊讶于名字的隐藏,因为它的工作原理与在 C++里是完全不同的。如果 Java 基础类
有一个方法名被“过载”使用多次,在衍生类里对那个方法名的重新定义就不会隐藏任何基础类的版本。所
以无论方法在这一级还是在一个基础类中定义,过载都会生效:
//: Hide。java
// Overloading a base…class method name
// in a derived class does not hide the
// base…class versions
class Homer {
char doh(char c) {
System。out。println(〃doh(char)〃);
return 'd' ;
}
148
…………………………………………………………Page 150……………………………………………………………
float doh(float f) {
System。out。println(〃doh(float)〃);
return 1。0f;
}
}
class Milhouse {}
class Bart extends Homer {
void doh(Milhouse m) {}
}
class Hide {
public static void main(String'' args) {
Bart b = new Bart();
b。doh (1); // doh(float) used
b。doh('x');
b。doh(1。0f);
b。doh(new Milhouse());
}
} ///:~
正如下一章会讲到的那样,很少会用与基础类里完全一致的签名和返回类型来覆盖同名的方法,否则会使人
感到迷惑(这正是 C++不允许那样做的原因,所以能够防止产生一些不必要的错误)。
6。4 到底选择合成还是继承
无论合成还是继承,都允许我们将子对象置于自己的新类中。大家或许会奇怪两者间的差异,以及到底该如
何选择。
如果想利用新类内部一个现有类的特性,而不想使用它的接口,通常应选择合成。也就是说,我们可嵌入一
个对象,使自己能用它实现新类的特性。但新类的用户会看到我们已定义的接口,而不是来自嵌入对象的接
口。考虑到这种效果,我们需在新类里嵌入现有类的private 对象。
有些时候,我们想让类用户直接访问新类的合成。也就是说,需要将成员对象的属性变为public。成员对象
会将自身隐藏起来,所以这是一种安全的做法。而且在用户知道我们准备合成一系列组件时,接口就更容易
理解。car (汽车)对象便是一个很好的例子:
//: Car。java
// position with public objects
class Engine {
public void start() {}
public void rev() {}
public void stop() {}
}
class Wheel {
public void inflate(int psi) {}
}
class Window {
public void rollup() {}
public void rolldown() {}
}
149
…………………………………………………………Page 151……………………………………………………………
class Door {
public Window window = new Window();
public void open() {}
public void close() {}
}
public class Car {
public Engine engine = new Engine();
public Wheel'' wheel = new Wheel'4';
public Door left = new Door();
right = new Door(); // 2…door
Car() {
for(int i = 0; i 《 4; i++)
wheel'i' = new Wheel();
}
public static void main(String'' args) {
Car car = new Car();
car。left。window。rollup();
car。wheel'0'。inflate(72);
}
} ///:~
由于汽车的装配是故障分析时需要考虑的一项因素(并非只是基础设计简单的一部分),所以有助于客户程
序员理解如何使用类,而且类创建者的编程复杂程度也会大幅度降低。
如选择继承,就需要取得一个现成的类,并制作它的一个特殊版本。通常,这意味着我们准备使用一个常规
用途的类,并根据特定的需求对其进行定制。只需稍加想象,就知道自己不能用一个车辆对象来合成一辆汽
车——汽车并不“包含”车辆;相反,它“属于”车辆的一种类别。“属于”关系是用继承来表达的,而
“包含”关系是用合成来表达的。
6。5 protected
现在我们已理解了继承的概念,protected 这个关键字最后终于有了意义。在理想情况下,private 成员随时
都是“私有”的,任何人不得访问。但在实际应用中,经常想把某些东西深深地藏起来,但同时允许访问衍
生类的成员。protected 关键字可帮助我们做到这一点。它的意思是“它本身是私有的,但可由从这个类继
承的任何东西或者同一个包内的其他任何东西访问”。也就是说,Java 中的protected 会成为进入“友好”
状态。
我们采取的最好的做法是保持成员的private 状态——无论如何都应保留对基 础的实施细节进行修改的权
利。在这一前提下,可通过protected 方法允许类的继承者进行受到控制的访问:
//: Orc。java
// The protected keyword
import java。util。*;
class Villain {
private int i;
protected int read() { return i; }
protected void set(int ii) { i = ii; }
public Villain(int ii) { i = ii; }
public int value(int m) { return m*i; }
}
public class Orc extends Villain {
150
…………………………………………………………Page 152……………………………………………………………
private int j;
public Orc(int jj) { super(jj); j = jj; }
public void change(int x) { set(x); }
} ///:~
可以看到,change()拥有对 set()的访问权限,因为它的属性是protected (受到保护的)。
6。6 累积开发
继承的一个好处是它支持“累积开发”,允许我们引入新的代码,同时不会为现有代码造成错误。这样可将
新错误隔离到新代码里。通过从一个现成的、功能性的类继承,同时增添成员新的数据成员及方法(并重新
定义现有方法),我们可保持现有代码原封不动(另外有人也许仍在使用它),不会为其引入自己的编程错
误。一旦出现错误,就知道它肯定是由于自己的新代码造成的。这样一来,与修改现有代码的主体相比,改
正错误所需的时间和精力就可以少很多。
类的隔离效果非常好,这是许多程序员事先没有预料到的。甚至不需要方法的源代码来实现代码的再生。最
多只需要导入一个包(这对于继承和合并都是成立的)。
大家要记住这样一个重点:程序开发是�
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!