友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
Java编程思想第4版[中文版](PDF格式)-第38部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
// is empty:
193
…………………………………………………………Page 195……………………………………………………………
if((next == (start + 1) % events。length)
&& looped)
return null;
} while(events'next' == null);
return events'next';
}
public void removeCurrent() {
events'next' = null;
}
}
public class Controller {
private EventSet es = new EventSet();
public void addEvent(Event c) { es。add(c); }
public void run() {
Event e;
while((e = es。getNext()) != null) {
if(e。ready()) {
e。action();
System。out。println(e。description());
es。removeCurrent();
}
}
}
} ///:~
EventSet 可容纳 100个事件(若在这里使用来自第 8 章的一个“真实”集合,就不必担心它的最大尺寸,因
为它会根据情况自动改变大小)。index (索引)在这里用于跟踪下一个可用的空间,而next (下一个)帮
助我们寻找列表中的下一个事件,了解自己是否已经循环到头。在对 getNext()的调用中,这一点是至关重
要的,因为一旦运行,Event 对象就会从列表中删去(使用 removeCurrent())。所以getNext()会在列表中
向前移动时遇到“空洞”。
注意removeCurrent()并不只是指示一些标志,指出对象不再使用。相反,它将句柄设为null 。这一点是非
常重要的,因为假如垃圾收集器发现一个句柄仍在使用,就不会清除对象。若认为自己的句柄可能象现在这
样被挂起,那么最好将其设为 null ,使垃圾收集器能够正常地清除它们。
Controller 是进行实际工作的地方。它用一个 EventSet 容纳自己的 Event 对象,而且 addEvent()允许我们
向这个列表加入新事件。但最重要的方法是run()。该方法会在EventSet 中遍历,搜索一个准备运行的
Event 对象——ready()。对于它发现ready()的每一个对象,都会调用action()方法,打印出
description(),然后将事件从列表中删去。
注意在迄今为止的所有设计中,我们仍然不能准确地知道一个“事件”要做什么。这正是整个设计的关键;
它怎样“将发生变化的东西同没有变化的东西区分开”?或者用我的话来讲,“改变的意图”造成了各类
Event 对象的不同行动。我们通过创建不同的Event 子类,从而表达出不同的行动。
这里正是内部类大显身手的地方。它们允许我们做两件事情:
(1) 在单独一个类里表达一个控制框架应用的全部实施细节,从而完整地封装与那个实施有关的所有东西。
内部类用于表达多种不同类型的action(),它们用于解决实际的问题。除此以外,后续的例子使用了
private 内部类,所以实施细节会完全隐藏起来,可以安全地修改。
(2) 内部类使我们具体的实施变得更加巧妙,因为能方便地访问外部类的任何成员。若不具备这种能力,代
码看起来就可能没那么使人舒服,最后不得不寻找其他方法解决。
现在要请大家思考控制框架的一种具体实施方式,它设计用来控制温室(Greenhouse)功能(注释④)。每
个行动都是完全不同的:控制灯光、供水以及温度自动调节的开与关,控制响铃,以及重新启动系统。但控
制框架的设计宗旨是将不同的代码方便地隔离开。对每种类型的行动,都要继承一个新的Event 内部类,并
在action() 内编写相应的控制代码。
194
…………………………………………………………Page 196……………………………………………………………
④:由于某些特殊原因,这对我来说是一个经常需要解决的、非常有趣的问题;原来的例子在《C++ Inside
& Out》一书里也出现过,但 Java 提供了一种更令人舒适的解决方案。
作为应用程序框架的一种典型行为,GreenhouseControls 类是从 Controller 继承的:
//: GreenhouseControls。java
// This produces a specific application of the
// control system; all in a single class。 Inner
// classes allow you to encapsulate different
// functionality for each type of event。
package c07。controller;
public class GreenhouseControls
extends Controller {
private boolean light = false;
private boolean water = false ;
private String thermostat = 〃Day〃;
private class LightOn extends Event {
public LightOn(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here to
// physically turn on the light。
light = true;
}
public String description() {
return 〃Light is on〃;
}
}
private class LightOff extends Event {
public LightOff(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here to
// physically turn off the light。
light = false;
}
public String description() {
return 〃Light is off〃;
}
}
private class WaterOn extends Event {
public WaterOn(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
water = true;
}
195
…………………………………………………………Page 197……………………………………………………………
public String description() {
return 〃Greenhouse water is on〃;
}
}
private class WaterOff extends Event {
public WaterOff(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
water = false;
}
public String description() {
return 〃Greenhouse water is off〃;
}
}
private class ThermostatNight extends Event {
public ThermostatNight(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
thermostat = 〃Night〃;
}
public String description() {
return 〃Thermostat on night setting〃;
}
}
private class ThermostatDay extends Event {
public ThermostatDay(long eventTime) {
super(eventTime);
}
public void action() {
// Put hardware control code here
thermostat = 〃Day〃;
}
public String description() {
return 〃Thermostat on day setting〃;
}
}
// An example of an action() that inserts a
// new one of itself into the event list:
private int rings;
private class Bell extends Event {
public Bell(long eventTime) {
super(eventTime);
}
public void action() {
// Ring bell every 2 seconds; rings times:
System。out。println(〃Bing!〃);
if(……rings 》 0)
addEvent(new Bell(
196
…………………………………………………………Page 198……………………………………………………………
System。currentTimeMillis() + 2000));
}
public String description() {
return 〃Ring bell〃;
}
}
private class Restart extends Event {
public Restart(long eventTime) {
super(eventTime);
}
public void action() {
long tm = System。currentTimeMillis();
// Instead of hard…wiring; you could parse
// configuration information from a text
// file here:
rings = 5;
addEvent(new ThermostatNight(tm));
addEvent(new LightOn(tm + 1000));
addEvent(new LightOff(tm + 2000));
addEvent(new WaterOn(tm + 3000));
addEvent(new WaterOff(tm + 8000));
addEvent(new Bell(tm + 9000));
addEvent(new ThermostatDay(tm + 10000));
// Can even add a Restart object!
addEvent(new Restart(tm + 20000));
}
public String description() {
return 〃Restarting system〃;
}
}
public static void main(String'' args) {
GreenhouseControls gc =
new GreenhouseControls();
long tm = System。currentTimeMillis();
gc。addEvent(gc。new Restart(tm));
gc。run();
}
} ///:~
注意 light (灯光)、water (供水)、thermostat (调温)以及rings 都隶属于外部类
GreenhouseControls ,所以内部类可以毫无阻碍地访问那些字段。此外,大多数action()方法也涉及到某些
形式的硬件控制,这通常都要求发出对非 Java 代码的调用。
大多数Event 类看起来都是相似的,但 Bell (铃)和Restart (重启)属于特殊情况。Bell 会发出响声,若
尚未响铃足够的次数,它会在事件列表里添加一个新的Bell 对象,所以以后会再度响铃。请注意内部类看起
来为什么总是类似于多重继承:Bell 拥有Event 的所有方法,而且也拥有外部类GreenhouseControls 的所
有方法。
Restart 负责对系统进行初始化,所以会添加所有必要的事件。当然,一种更灵活的做法是避免进行“硬编
码”,而是从一个文件里读入它们(第 10章的一个练习会要求大家修改这个例子,从而达到这个目标)。由
于Restart()仅仅是另一个 Event 对象,所以也可以在Restart。action()里添加一个 Restart 对象,使系统
能够定期重启。在main()中,我们需要做的全部事情就是创建一个 GreenhouseControls 对象,并添加一个
Restart 对象,令其工作起来。
这个例子应该使大家对内部类的价值有一个更加深刻的认识,特别是在一个控制框架里使用它们的时候。此
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〃);
Amphibi
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!