# 16.8 RTTI真的有害吗
本章的各种设计模式都在努力避免使用RTTI,这或许会给大家留下“RTTI有害”的印象(还记得可怜的`goto`吗,由于给人印象不佳,根本就没有放到Java里来)。但实际情况并非绝对如此。正确地说,应该是RTTI使用不当才“有害”。我们之所以想避免RTTI的使用,是由于它的错误运用会造成扩展性受到损害。而我们事前提出的目标就是能向系统自由加入新类型,同时保证对周围的代码造成尽可能小的影响。由于RTTI常被滥用(让它查找系统中的每一种类型),会造成代码的扩展能力大打折扣——添加一种新类型时,必须找出使用了RTTI的所有代码。即使仅遗漏了其中的一个,也不能从编译器那里得到任何帮助。
然而,RTTI本身并不会自动产生非扩展性的代码。让我们再来看一看前面提到的垃圾回收例子。这一次准备引入一种新工具,我把它叫作`TypeMap`。其中包含了一个`Hashtable`(散列表),其中容纳了多个`Vector`,但接口非常简单:可以添加(`add()`)一个新对象,可以获得(`get()`)一个`Vector`,其中包含了属于某种特定类型的所有对象。对于这个包含的散列表,它的关键在于对应的`Vector`里的类型。这种设计模式的优点(根据Larry O'Brien的建议)是在遇到一种新类型的时候,`TypeMap`会动态加入一种新类型。所以不管什么时候,只要将一种新类型加入系统(即使在运行期间添加),它也会正确无误地得以接受。
我们的例子同样建立在`c16.Trash`这个“包”(`Package`)内的`Trash`类型结构的基础上(而且那儿使用的`Trash.dat`文件可以照搬到这里来)。
```
//: DynaTrash.java
// Using a Hashtable of Vectors and RTTI
// to automatically sort trash into
// vectors. This solution, despite the
// use of RTTI, is extensible.
package c16.dynatrash;
import c16.trash.*;
import java.util.*;
// Generic TypeMap works in any situation:
class TypeMap {
private Hashtable t = new Hashtable();
public void add(Object o) {
Class type = o.getClass();
if(t.containsKey(type))
((Vector)t.get(type)).addElement(o);
else {
Vector v = new Vector();
v.addElement(o);
t.put(type,v);
}
}
public Vector get(Class type) {
return (Vector)t.get(type);
}
public Enumeration keys() { return t.keys(); }
// Returns handle to adapter class to allow
// callbacks from ParseTrash.fillBin():
public Fillable filler() {
// Anonymous inner class:
return new Fillable() {
public void addTrash(Trash t) { add(t); }
};
}
}
public class DynaTrash {
public static void main(String[] args) {
TypeMap bin = new TypeMap();
ParseTrash.fillBin("Trash.dat",bin.filler());
Enumeration keys = bin.keys();
while(keys.hasMoreElements())
Trash.sumValue(
bin.get((Class)keys.nextElement()));
}
} ///:~
```
尽管功能很强,但对`TypeMap`的定义是非常简单的。它只是包含了一个散列表,同时`add()`负担了大部分的工作。添加一个新类型时,那种类型的`Class`对象的引用会被提取出来。随后,利用这个引用判断容纳了那类对象的一个`Vector`是否已存在于散列表中。如答案是肯定的,就提取出那个`Vector`,并将对象加入其中;反之,就将`Class`对象及新`Vector`作为一个“键-值”对加入。
利用`keys()`,可以得到对所有`Class`对象的一个“枚举”(`Enumeration`),而且可用`get()`,可通过`Class`对象获取对应的`Vector`。
`filler()`方法非常有趣,因为它利用了`ParseTrash.fillBin()`的设计——不仅能尝试填充一个`Vector`,也能用它的`addTrash()`方法试着填充实现了`Fillable`(可填充)接口的任何东西。`filter()`需要做的全部事情就是将一个引用返回给实现了`Fillable`的一个接口,然后将这个引用作为参数传递给`fillBin()`,就象下面这样:
```
ParseTrash.fillBin("Trash.dat", bin.filler());
```
为产生这个引用,我们采用了一个“匿名内部类”(已在第7章讲述)。由于根本不需要用一个已命名的类来实现`Fillable`,只需要属于那个类的一个对象的引用即可,所以这里使用匿名内部类是非常恰当的。
对这个设计,要注意的一个地方是尽管没有设计成对归类加以控制,但在`fillBin()`每次进行归类的时候,都会将一个`Trash`对象插入`bin`。
通过前面那些例子的学习,`DynaTrash`类的大多数部分都应当非常熟悉了。这一次,我们不再将新的`Trash`对象置入类型`Vector`的一个`bin`内。由于`bin`的类型为`TypeMap`,所以将垃圾(`Trash`)丢进垃圾筒(`Bin`)的时候,`TypeMap`的内部归类机制会立即进行适当的分类。在`TypeMap`里遍历并对每个独立的`Vector`进行操作,这是一件相当简单的事情:
```
Enumeration keys = bin.keys();
while(keys.hasMoreElements())
Trash.sumValue(
bin.get((Class)keys.nextElement()));
```
就象大家看到的那样,新类型向系统的加入根本不会影响到这些代码,亦不会影响`TypeMap`中的代码。这显然是解决问题最圆满的方案。尽管它确实严重依赖RTTI,但请注意散列表中的每个键-值对都只查找一种类型。除此以外,在我们增加一种新类型的时候,不会陷入“忘记”向系统加入正确代码的尴尬境地,因为根本就没有什么代码需要添加。
- Java 编程思想
- 写在前面的话
- 引言
- 第1章 对象入门
- 1.1 抽象的进步
- 1.2 对象的接口
- 1.3 实现方案的隐藏
- 1.4 方案的重复使用
- 1.5 继承:重新使用接口
- 1.6 多态对象的互换使用
- 1.7 对象的创建和存在时间
- 1.8 异常控制:解决错误
- 1.9 多线程
- 1.10 永久性
- 1.11 Java和因特网
- 1.12 分析和设计
- 1.13 Java还是C++
- 第2章 一切都是对象
- 2.1 用引用操纵对象
- 2.2 所有对象都必须创建
- 2.3 绝对不要清除对象
- 2.4 新建数据类型:类
- 2.5 方法、参数和返回值
- 2.6 构建Java程序
- 2.7 我们的第一个Java程序
- 2.8 注释和嵌入文档
- 2.9 编码样式
- 2.10 总结
- 2.11 练习
- 第3章 控制程序流程
- 3.1 使用Java运算符
- 3.2 执行控制
- 3.3 总结
- 3.4 练习
- 第4章 初始化和清除
- 4.1 用构造器自动初始化
- 4.2 方法重载
- 4.3 清除:收尾和垃圾收集
- 4.4 成员初始化
- 4.5 数组初始化
- 4.6 总结
- 4.7 练习
- 第5章 隐藏实现过程
- 5.1 包:库单元
- 5.2 Java访问指示符
- 5.3 接口与实现
- 5.4 类访问
- 5.5 总结
- 5.6 练习
- 第6章 类复用
- 6.1 组合的语法
- 6.2 继承的语法
- 6.3 组合与继承的结合
- 6.4 到底选择组合还是继承
- 6.5 protected
- 6.6 累积开发
- 6.7 向上转换
- 6.8 final关键字
- 6.9 初始化和类装载
- 6.10 总结
- 6.11 练习
- 第7章 多态性
- 7.1 向上转换
- 7.2 深入理解
- 7.3 覆盖与重载
- 7.4 抽象类和方法
- 7.5 接口
- 7.6 内部类
- 7.7 构造器和多态性
- 7.8 通过继承进行设计
- 7.9 总结
- 7.10 练习
- 第8章 对象的容纳
- 8.1 数组
- 8.2 集合
- 8.3 枚举器(迭代器)
- 8.4 集合的类型
- 8.5 排序
- 8.6 通用集合库
- 8.7 新集合
- 8.8 总结
- 8.9 练习
- 第9章 异常差错控制
- 9.1 基本异常
- 9.2 异常的捕获
- 9.3 标准Java异常
- 9.4 创建自己的异常
- 9.5 异常的限制
- 9.6 用finally清除
- 9.7 构造器
- 9.8 异常匹配
- 9.9 总结
- 9.10 练习
- 第10章 Java IO系统
- 10.1 输入和输出
- 10.2 增添属性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File类
- 10.5 IO流的典型应用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 压缩
- 10.9 对象序列化
- 10.10 总结
- 10.11 练习
- 第11章 运行期类型识别
- 11.1 对RTTI的需要
- 11.2 RTTI语法
- 11.3 反射:运行期类信息
- 11.4 总结
- 11.5 练习
- 第12章 传递和返回对象
- 12.1 传递引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只读类
- 12.5 总结
- 12.6 练习
- 第13章 创建窗口和程序片
- 13.1 为何要用AWT?
- 13.2 基本程序片
- 13.3 制作按钮
- 13.4 捕获事件
- 13.5 文本字段
- 13.6 文本区域
- 13.7 标签
- 13.8 复选框
- 13.9 单选钮
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 视窗化应用
- 13.16 新型AWT
- 13.17 Java 1.1用户接口API
- 13.18 可视编程和Beans
- 13.19 Swing入门
- 13.20 总结
- 13.21 练习
- 第14章 多线程
- 14.1 反应灵敏的用户界面
- 14.2 共享有限的资源
- 14.3 堵塞
- 14.4 优先级
- 14.5 回顾runnable
- 14.6 总结
- 14.7 练习
- 第15章 网络编程
- 15.1 机器的标识
- 15.2 套接字
- 15.3 服务多个客户
- 15.4 数据报
- 15.5 一个Web应用
- 15.6 Java与CGI的沟通
- 15.7 用JDBC连接数据库
- 15.8 远程方法
- 15.9 总结
- 15.10 练习
- 第16章 设计模式
- 16.1 模式的概念
- 16.2 观察器模式
- 16.3 模拟垃圾回收站
- 16.4 改进设计
- 16.5 抽象的应用
- 16.6 多重分发
- 16.7 访问器模式
- 16.8 RTTI真的有害吗
- 16.9 总结
- 16.10 练习
- 第17章 项目
- 17.1 文字处理
- 17.2 方法查找工具
- 17.3 复杂性理论
- 17.4 总结
- 17.5 练习
- 附录A 使用非JAVA代码
- 附录B 对比C++和Java
- 附录C Java编程规则
- 附录D 性能
- 附录E 关于垃圾收集的一些话
- 附录F 推荐读物