天天看點

Java程式設計思想之類型資訊(Class對象)

運作時類型資訊使得你可以在程式運作時發現和使用類型資訊

如何讓我們在運作時識别對象和類的資訊的?

主要有兩種方式:一種是“傳統的”RTTI (運作時類型識别),它假定我們在編譯時已經知道了所有的類型;另一種是反射機制,它允許我們在運作時發現和使用類的資訊。

1 為什麼需要RTTI?

對于一個類層次結構,基類是Shape,而派生出的具體類有Circle、Square和Triangle。

面向對象程式設計中基本的目的是:讓代碼隻操縱對基類的引用。

List<Shape> shapeList = Arrays.asList(
    new Circle(), new Square(),new Triangle()
);
for(Shape shape : shapeList)
    shape.draw();  // Shape對象實際上執行什麼樣的代碼,是由引用所指向的具體對象Circle、Square或者Triangle決定的——多态機制。
           

當把Shape對象放入List< Shape >時,會向上轉型。但在向上轉型為Shape的時候也丢失了Shape對象的具體類型。對于容器而言,它們隻是Shape類的對象。

當從容器中取出元素時,實際上它将所有的事物都當做 Object 持有——會自動将結果轉型為Shape。這是RTTI最基本的使用形式,因為在Java中所有的類型轉換都是在運作時進行正确性的檢查的。這也是RTTI名字的含義:在運作時,識别一個對象的類型。

使用RTTI,可以查詢某個Shape引用所指向的對象的确切類型,然後選擇或剔除特例。

2 Class對象

要了解RTTI在Java中的工作原理,首先必須知道類型資訊在運作時是如何表示的。這項工作是由稱為Class對象的特殊對象完成的,它包含了與類有關的資訊。

類是程式的一部分,每個類都有一個Class對象。換而言之,每當編寫并且編譯了一個新類,就會産生一個Class對象(更恰當地說,是被儲存在一個同名的.class檔案中)。為了生成這個類的對象,運作這個程式的JVM将使用被稱為“類加載器”的子系統。

所有的類都是在對其第一次使用時,動态加載到JVM中的。當程式建立第一個對類的靜态成員的引用時,就會加載這個類(這個證明構造器也是類的靜态方法)。是以,Java程式在它開始運作之前并非被完全加載,其各個部分是在必需時才加載的。類加載器首先檢查這個類的Class對象是否已經加載。如果尚未加載,預設的類加載器就會根據類名查找.class檔案。這個類的位元組碼被加載時,它們會接受驗證,以確定其沒有被破壞,并且不包含不良的Java代碼。

一旦某個類的Class對象被載入記憶體,它就被用來建立這個類的所有對象。

Class.forName(類的全限定名);

無論何時,隻要你想在運作時使用類型資訊,就必須首先獲得對恰當的Class對象的引用。

Class.forName() 就是實作此功能的便捷途徑,因為你不需要為了獲得Class引用而持有該類型的對象。但是,如果你已經擁有了一個感興趣的類型的對象,那就可以通過調用getClass()方法來擷取Class引用了,這個方法屬于根類Object的一部分,它将傳回表示該對象的實際類型的Class引用。

Class的newInstance()方法是實作“虛拟構造器”的一種途徑,虛拟構造器允許你聲明:“我不知道你的确切類型,但無論如何要正确地建立你自己。”使用newInstance()來建立的類,必須帶有預設的構造器。

類字面常量

Java還提供了另一種方法來生成對Class對象的引用,即使用類字面常量。

類名.class;

這樣做不僅簡單,而且更安全,因為它在編譯時就會受到檢查(是以不需要置于try語句塊中)。類字面常量不僅可以應用于普通的類,也可應用于接口、數組以及基本資料類型。

當使用“.class” 來建立對Class對象的引用時,不會自動地初始化該Class對象。為了使用類而做的準備工作實際包含三個步驟:

1. 加載,這是由類加載器執行的。查找位元組碼,并從這些位元組碼中建立一個Class對象。

2. 連結 ,驗證類中的位元組碼,為靜态域配置設定存儲空間,并且如果必需的話,将解析這個類建立 的對其它類的所有引用。

3. 初始化 ,如果該類具有超類,則對其初始化,執行靜态初始化器和靜态初始化塊。

初始化被延遲到了對靜态方法或者非常數靜态域進行首次引用時才執行。

類型轉換前先做檢查

我們已知的RTTI形式包括:

1. 傳統的類型轉換,如”(Shape)“,由RTTI確定類型轉換的正确性,如果執行了一個錯誤的類型轉換,就會抛出一個ClassCastException異常。

2. 代表對象的類型的Class對象。通過查詢Class對象可以擷取運作時所需的資訊。

3. 關鍵字instanceof,它傳回一個布爾值,已判定對象是不是某個特定類型的執行個體。

x instanceof 類名 //檢查對象x是否從屬于Dog類,隻可将其與命名類型進行比較,而不能與Class對象作比較。

動态的instanceof :Class.isInstance方法提供了一種動态地測試對象的途徑。

類名.class.isInstance(x) //你是這個類或者你是這個類的派生類嗎?

在編譯時,編譯器必須要知道所有要通過RTTI來處理的類;RTTI允許通過匿名基類的引用來發現類型資訊

面向對象程式設計語言的目的是讓我們在凡是可以使用的地方都使用多态機制,隻在必須的時候使用RTTI。

繼續閱讀