# 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 推薦讀物