- 写在前面的话
- 引言
- 第 1 章 对象入门
- 第 2 章 一切都是对象
- 第 3 章 控制程序流程
- 第 4 章 初始化和清除
- 第 5 章 隐藏实施过程
- 第 6 章 类再生
- 第 7 章 多形性
- 第 8 章 对象的容纳
- 第 9 章 违例差错控制
- 第 10 章 Java IO 系统
- 第 11 章 运行期类型鉴定
- 第 12 章 传递和返回对象
- 第 十三 章 创建窗口和程序片
- 第 14 章 多线程
- 第 15 章 网络编程
- 第 16 章 设计范式
- 第 17 章 项目
- 附录 A 使用非 JAVA 代码
- 附录 B 对比 C++和 Java
- 附录 C Java 编程规则
- 附录 D 性能
- 附录 E 关于垃圾收集的一些话
- 附录 F 推荐读物
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,但请注意散列表中的每个键-值对都只查找一种类型。除此以外,在我们增加一种新类型的时候,不会陷入“忘记”向系统加入正确代码的尴尬境地,因为根本就没有什么代码需要添加。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论