10:檢測類型
  運行時類型識別(run-time type identification,縮寫為RTTI)。

為什么會需要RTTI
  collection是一種工具,它只有一種用途,就是要為你保管其它對象。因此出于通用性的考慮,這些collection應該能持有任何東西。所以它們持有Object。
 
  Class對象
    想要知道JAVA的RTTI是如何工作的,你就必須首先知道程序運行的時候,類型信息是怎樣表示的。這是由一種特殊的,保存類的信息的,叫做“Class對象(Class object)”的對象來完成。實際上類的“常規”對象是由Class對象創建的。
    程序里的每個類都要有一個Class對象。也就是說,每次你撰寫并且編譯了一個新的類的時候,你就創建了一個新的Class對象(而且可以這么說,這個對象會存儲在同名的.class文件里)。程序運行時,當你需要創建一個那種類的對象的時候,JVM會檢查它是否裝載了那個Class對象。如果沒有, JVM就會去找那個.class文件,然后裝載。由此也可知道,Java程序在啟動的時候并沒有完全裝載,這點同許多傳統語言是不一樣的。
   
    Class.forName("一個類的名字");
    這是一個Class的static方法(所有的Class對象所共有的)。Class對象同其它對象一樣,也可以用reference來操控(這是裝載器要干的),而要想獲取其reference, forName()就是一個辦法。它要一個表示這個類的名字的String作參數(一定要注意拼寫喝大小寫!)。這個方法會返回Class的 reference,還有一個副作用,看看這個String所說的那個類裝載了沒有,要是還沒有那就馬上裝載。如果Class.forName()沒有找到它要裝載的類,就會拋出一個ClassNotFoundException。
   
    Class常數
    Java還提供了一種獲取Class對象的reference的方法:“class常數(class literal)”。
      類的名字.class;
    這種寫法不但更簡單,而且也更安全,因為它是在編譯時做檢查的。此外由于沒有方法調用,它的執行效率也更高一些。
   
    Class常數不但能用于普通類,也可以用于接口,數組和primitive類型。此外,每種primitive的wrapper類還有一個標準的,名為 TYPE的數據成員。這個TYPE能返回“與這種primitive相關聯的wrapper類”的Class對象的reference,就像這樣:
   
         ... 等同于 ...
    boolean.class    Boolean.TYPE
    char.class       Character.TYPE
    byte.class       Byte.TYPE
    short.class      Short.TYPE
    int.class        Integer.TYPE
    long.class       Long.TYPE
    float.class      Float.TYPE
    double.class     Double.TYPE
    void.class       Void.TYPE
   
    我喜歡盡量使用“.class”,因為這種寫法與普通類的保持一致。
 
  轉換之前先作檢查
    到目前為止,你看到的RTTI的形式有:
      1。經典的類型轉換:如“(Shape)”,這種轉換要經過RTTI的檢查。要是做了錯誤的轉換,它就會拋出ClassCastException。
      2.代表對象類型的Class對象。你可以在運行的時候查詢Class對象,以此來獲取所需的信息。
   
    如果不進行明確的類型轉換的話,編譯器時不會讓你把對象賦給派生類的reference的。
 
  Java里面還有第三種RTTI的形式。這就是instanceof關鍵詞,它會告訴你對象是不是某個類的實例。它返回的是一個boolean值。
 
  使用類常數
 
  動態的instanceof
    isInstance()能完全替代instanceof。
 
  instanceof vs. Class的相等性

RTTI的語法
 
  Class.getInterfaces()方法會返回一個Class對象的數組。數組中的對象分別表示它所實現的接口。
  如果你手上有一個Class對象,你還能用getSuperclass()問出它最近的那個父類。當然,這會返回一個Class的reference,于是你可以接著問,程序運行的時候,你能以此發現對象的完整的關系。
  Class的newInstance()方法就像是另一種clone()對象的方法。但是,你卻可以用newInstance()憑空創建出一個新的對象。
  printInfo()方法,它拿一個Class對象的reference作參數,用getName()提取類的名字,用isInterface()判斷它是不是接口。這樣,你就能僅憑Class對象就找出所有你想知道的這個對象的信息了。

Reflection:運行時的類信息
  Java以JavaBeans的形式提供了基于組件的編程的支持。
  通過網絡在遠程機器上創建對象并運行程序。這被成為“遠程方法調用(Remote Method Invocation縮寫是RMI)”。它能讓一個Java程序將對象分布到很多機器上。
  除了Class類,還有一個類庫,java.lang.reflect也支持reflection。這個類庫里面有Field,Method,和 Constructor類(它們都實現了Member接口)運行時,JVM會創建一些這種類的對象來代表未知類的各個成員。然后,你就能用 Constructor來創建新的對象,用get()和set()來讀取和修改與Field隊形愛女嘎相關聯的成員數據,用invoke()方法調用與 Method對象相關聯的方法了。此外,你還能用getFields(),getMethods(),getConstructors()之類的方法,獲取表示成員數據,方法或構造函數的對象數組。由此,即便編譯時什么信息都得不到,你也有辦法能在運行時問出匿名對象的全部類型信息了。
  有一點很重要,reflection不是什么妖術。當你用reflection與未知類的對象打交道的時候,JVM(會和普通的RTTI一樣)先看看這個對象是屬于那個具體類型的,但是此后,它還是得先裝載Class對象才能工作。也就是,不管是從本地還是從網絡,反正JVM必須拿到那個.class文件。所以RTTI同reflection的真正區別在于,RTTI是在編譯時讓編譯器打開并且檢查.class文件。換句話說,你是在通過“正常”途徑調用對象的方法。而對reflection來說,編譯時是得不到.class文件的;所以它是在運行時打開并檢查那個文件。
 
  一個提取類的方法的程序
    一般來說,你不太會直接使用reflection;Java之所以要有這種功能是要用它來支持一些憋的特性,比如對象的序列化和JavaBeans。不過在有些情況下,能動態提取類的信息還是很有用的。
    Class的getMethods()和getConstructors()方法分別會返回一個Method和一個Constructor數組。這兩個類又包括一些“能把它們所代表的方法的名字,參數,返回值全部拆解開來”的方法。不過你也可以像這里所作的,只用toString()去獲取一個包括這個方法的全部特征簽名的String。剩下的代碼就是用來抽取命令行信息,以及判斷方法特征是否與你輸入的字符串相匹配(用indexOf()),并且把匹配的方法列出來的。


總結:
  RTTI能讓你用一個匿名的基類reference來獲取對象的確切類型的信息。在不懂多臺方法調用的時候,這么作是理所當然的,因此新手們會自然而然的想到它,于是就用錯了地方,對很多從面向過程的編程語言轉過來的人來說,剛開始的時候,它們還不習慣扔掉switch語句。于是當他們用RTTI來編程的時候,就會錯過多態性所帶來的編程和代碼維護方面的好處。Java的本義是讓你在程序里面全程使用多態性,知識在萬不得已的情況下才使用RTTI。
  但是要想正確地使用多臺方法調用,你就必須要能控制基類的定義,因為當你擴展程序的時候,可能會發現基類里面沒有你想要的方法。如果這個基類是來自類庫的,或是由別人控制的,那么RTTI就成解決方案了:你可以繼承一個新的類,然后加上你自己的方法。在程序的其他地方,你可以檢測出這個類型,調用那些特殊的方法。這樣做不會破壞多態性,也不影響程序的擴展性,因為加一個新的類型不會要你去到處修改switch語句。但是,如果是在程序的主體部分加入要使用新特性的嗲馬的話,你就必須使用RTTI來檢查對象的確切類型了。
  RTTI還會被用來解決效率問題。假設你寫了一個很好的多臺程序,但是運行的時候發現,有個對象反映奇慢。于是,你就可以用RTTI把則個對象撿出來,然后專門針對它的問題寫代碼以提高程序的運行效率,不過編程的時候切忌去過早有話代碼。這是一個很有誘惑的陷阱。最好還是先讓程序跑起來,然后再判斷一下它跑得是不是夠快了。只有覺得它還不夠快,你才應該去著手解決效率問題--用profiler。




                                                                                 2005年03月19日 12:55 PM