按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
可以指定一旦有人试图覆盖该文件就“掷”出一个违例——有的编程系统允许我们自行指定想打开一个输出
文件,但唯一的前提是它尚不存在。但在 Java 中,似乎必须用一个File 对象来判断某个文件是否存在,因
为假如将其作为FileOutputStream 或者FileWriter 打开,那么肯定会被覆盖。若同时指定文件和目录路
径,File 类设计上的一个缺陷就会暴露出来,因为它会说“不要试图在单个类里做太多的事情”!
IO流库易使我们混淆一些概念。它确实能做许多事情,而且也可以移植。但假如假如事先没有吃透装饰器方
案的概念,那么所有的设计都多少带有一点盲目性质。所以不管学它还是教它,都要特别花一些功夫才行。
而且它并不完整:没有提供对输出格式化的支持,而其他几乎所有语言的 IO包都提供了这方面的支持(这一
点没有在Java 1。1 里得以纠正,它完全错失了改变库设计方案的机会,反而增添了更特殊的一些情况,使复
杂程度进一步提高)。Java 1。1 转到那些尚未替换的 IO 库,而不是增加新库。而且库的设计人员似乎没有
很好地指出哪些特性是不赞成的,哪些是首选的,造成库设计中经常都会出现一些令人恼火的反对消息。
然而,一旦掌握了装饰器方案,并开始在一些较为灵活的环境使用库,就会认识到这种设计的好处。到那个
时候,为此多付出的代码行应该不至于使你觉得太生气。
10。11 练习
(1) 打开一个文本文件,每次读取一行内容。将每行作为一个 String 读入,并将那个String 对象置入一个
Vector 里。按相反的顺序打印出 Vector 中的所有行。
(2) 修改练习 1,使读取那个文件的名字作为一个命令行参数提供。
(3) 修改练习2,又打开一个文本文件,以便将文字写入其中。将 Vector 中的行随同行号一起写入文件。
(4) 修改练习2,强迫Vector 中的所有行都变成大写形式,将结果发给 System。out。
(5) 修改练习2,在文件中查找指定的单词。打印出包含了欲找单词的所有文本行。
(6) 在Blips。java 中复制文件,将其重命名为BlipCheck。java。然后将类Blip2 重命名为BlipCheck (在进
程中将其标记为public)。删除文件中的//!记号,并执行程序。接下来,将BlipCheck 的默认构建器变成
注释信息。运行它,并解释为什么仍然能够工作。
(7) 在Blip3。java 中,将接在“You must do this:”字样后的两行变成注释,然后运行程序。解释得到的
结果为什么会与执行了那两行代码不同。
(8) 转换SortedWordCount。java 程序,以便使用Java 1。1 IO 流。
(9) 根据本章正文的说明修改程序 CADState。java 。
(10) 在第 7 章(中间部分)找到GreenhouseControls。java 示例,它应该由三个文件构成。在
GreenhouseControls。java 中,Restart()内部类有一个硬编码的事件集。请修改这个程序,使其能从一个文
本文件里动态读取事件以及它们的相关时间。
332
…………………………………………………………Page 334……………………………………………………………
第 11 章 运行期类型鉴定
运行期类型鉴定(RTTI )的概念初看非常简单——手上只有基础类型的一个句柄时,利用它判断一个对象的
正确类型。
然而,对RTTI 的需要暴露出了面向对象设计许多有趣(而且经常是令人困惑的)的问题,并把程序的构造问
题正式摆上了桌面。
本章将讨论如何利用Java 在运行期间查找对象和类信息。这主要采取两种形式:一种是“传统”RTTI ,它假
定我们已在编译和运行期拥有所有类型;另一种是 Java1。1 特有的“反射”机制,利用它可在运行期独立查
找类信息。首先讨论“传统”的RTTI,再讨论反射问题。
11。1 对 RTTI 的需要
请考虑下面这个熟悉的类结构例子,它利用了多形性。常规类型是Shape 类,而特别衍生出来的类型是
Circle,Square 和Triangle 。
这是一个典型的类结构示意图,基础类位于顶部,衍生类向下延展。面向对象编程的基本目标是用大量代码
控制基础类型(这里是 Shape)的句柄,所以假如决定添加一个新类(比如Rhomboid ,从Shape 衍生),从
而对程序进行扩展,那么不会影响到原来的代码。在这个例子中,Shape 接口中的动态绑定方法是draw(),
所以客户程序员要做的是通过一个普通Shape 句柄调用draw()。draw()在所有衍生类里都会被覆盖。而且由
于它是一个动态绑定方法,所以即使通过一个普通的Shape 句柄调用它,也有表现出正确的行为。这正是多
形性的作用。
所以,我们一般创建一个特定的对象(Circle,Square,或者 Triangle ),把它上溯造型到一个Shape (忽
略对象的特殊类型),以后便在程序的剩余部分使用匿名 Shape 句柄。
作为对多形性和上溯造型的一个简要回顾,可以象下面这样为上述例子编码(若执行这个程序时出现困难,
请参考第3 章3。1。2 小节“赋值”):
//: Shapes。java
package c11;
import java。util。*;
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System。out。println(〃Circle。draw()〃);
}
}
class Square implements Shape {
333
…………………………………………………………Page 335……………………………………………………………
public void draw() {
System。out。println(〃Square。draw()〃);
}
}
class Triangle implements Shape {
public void draw() {
System。out。println(〃Triangle。draw()〃);
}
}
public class Shapes {
public static void main(String'' args) {
Vector s = new Vector();
s。addElement(new Circle());
s。addElement(new Square());
s。addElement(new Triangle());
Enumeration e = s。elements();
while(e。hasMoreElements())
((Shape)e。nextElement())。draw();
}
} ///:~
基础类可编码成一个 interface (接口)、一个abstract (抽象)类或者一个普通类。由于Shape 没有真正
的成员(亦即有定义的成员),而且并不在意我们创建了一个纯粹的Shape 对象,所以最适合和最灵活的表
达方式便是用一个接口。而且由于不必设置所有那些abstract 关键字,所以整个代码也显得更为清爽。
每个衍生类都覆盖了基础类draw 方法,所以具有不同的行为。在main()中创建了特定类型的Shape,然后将
其添加到一个Vector。这里正是上溯造型发生的地方,因为Vector 只容纳了对象。由于Java 中的所有东西
(除基本数据类型外)都是对象,所以Vector 也能容纳 Shape 对象。但在上溯造型至 Object 的过程中,任
何特殊的信息都会丢失,其中甚至包括对象是几何形状这一事实。对Vect or 来说,它们只是Object。
用nextElement()将一个元素从 Vector 提取出来的时候,情况变得稍微有些复杂。由于 Vector 只容纳
Object,所以 nextElement()会自然地产生一个 Object 句柄。但我们知道它实际是个 Shape 句柄,而且希望
将Shape 消息发给那个对象。所以需要用传统的〃(Shape)〃方式造型成一个Shape。这是RTTI 最基本的形
式,因为在 Java 中,所有造型都会在运行期间得到检查,以确保其正确