要理解RTTI在Java中的工作原理,首先必須知道類型信息在運行時是如何表示的,這項工程由Class對象完成,它包含了與類有關的信息。Java使用Class對象來執行其RTTI,即使你執行的是類似轉型這樣的操作。
Java程序在運行時,Java運行時系統一直對所有的對象進行所謂的運行時類型標識。這項信息紀錄了每個對象所屬的類。虛擬機通常使用運行時類型信息選准正確方法去執行,用來保存這些類型信息的類是Class類。Class類封裝一個對象和接口運行時的狀態,當裝載類時,Class類型的對象自動創建。
Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的 defineClass 方法自動構造的,因此不能顯式地聲明一個Class對象。
虛擬機為每種類型管理一個獨一無二的Class對象。也就是說,每個類(型)都有一個Class對象。托福網課運行程序時,Java虛擬機(JVM)首先檢查是否所要加載的類對應的Class對象是否已經加載。如果沒有加載,JVM就會根據類名查找.class文件,並將其Class對象載入。
基本的 Java 類型(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也都對應一個 Class 對象。
每個數組屬於被映射為 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class 對象。
一般某個類的Class對象被載入內存,它就用來創建這個類的所有對象。
獲取Class對象的方式
1、調用Object類的getClass()方法來得到Class對象,這也是最常見的產生Class對象的方法。例如:
MyObject x;
Class c1=x.getClass();
2、使用Class類的中靜態forName()方法獲得與字符串對應的Class對象。例如:
Class c2=Class.forName(“MyObject”),Employee必須是接口或者類的名字。
3、獲取Class類型對象的第三個方法非常簡單。如果T是一個Java類型,那么T.class就代表了匹配的類對象。例如
Class cl1=Manager.class;
Class cl2=int.class;
Class cl3=Double[].class;
注意:Class對象實際上描述的只是類型,而這類型未必是類或者接口。例如上面的int.class是一個Class類型的對象。由於歷史原因,數組類型的getName方法會返回奇怪的名字。
其中關於加載,有些主要注意的問題:
當使用“.class”來創建Class對象的引用時,不會自動的初始化該Class對象,sat培訓為了使用類而做的准備工作實際包含三個步驟:
(1)裝載
(2)連接
(3)初始化
其中裝載階段又三個基本動作組成:
另外如果一個類裝載器在預先裝載的時遇到缺失或錯誤的class文件,它需要等到程序首次主動使用該類時才報告錯誤。
連接階段又分為三部分:
當一個類被主動使用時,Java虛擬就會對其初始化,如下六種情況為主動使用:
Java編譯器會收集所有的類變量初始化語句和類型的靜態初始化器,將這些放到一個特殊的方法中:clinit。
實際上,static塊的執行發生在“初始化”的階段。初始化階段,jvm主要完成對靜態變量的初始化,七年級英語單詞表靜態塊執行等工作。
下面我們看看執行static塊的幾種情況:
1、第一次new A()的過程會打印”“;因為這個過程包括了初始化
2、第一次Class.forName(“A”)的過程會打印”“;因為這個過程相當於Class.forName(“A”,true,this.getClass().getClassLoader());
3、第一次Class.forName(“A”,false,this.getClass().getClassLoader())的過程則不會打印”“。因為false指明了裝載類的過程中,不進行初始化。不初始化則不會執行static塊。
參考資料:深入Java虛擬機
接下來實際舉兩個例子
輸出結果:
會發現,初始化被延遲到了進行首次引用時才執行。引用會調用靜態代碼塊,當你第一次使用引用的時候就會發生如上情況(JVM原理中的懶加載)
同理的例子:
在第二種情況就產生了調用。
還需要額外注意的問題就是static final是“編譯器常量”,像Initable.staticFinal那樣,那個這個值不需要對Initable類進行初始化就可以被讀取。但是,如果只是將一個域設置為static和final的,還不足以確保這種行為,例如Initable.staticFinal2的訪問將對其將強制進行類的初始化,因為它不是一個編譯器常量。
如果一個static域不是final的,那么在對它訪問時,總是要求在它被讀取之前,要先進性鏈接(為這個域分配存儲空間)和初始化(初始化該存儲空間)。
參考資料:Thinking in Java