14.2java
RTTI運行時類型識別。數據庫
Class對象包含了與類有關的信息,Java使用Class對象來執行其RTTI。每一個類都有一個Class對象,每當編寫而且編譯了一個新類,就會產生一個Class對象。爲了生成這個類的對象,JVM使用類加載器子系統。類加載器子系統能夠包含一條類加載器鏈:編程
全部的類都是在對其第一次使用時,動態加載到JVM中的。當程序建立第一個對類的靜態成員引用時,就會加載這個類。注意構造函數也是類的靜態方法。使用new操做符建立類的新對象也會被看成對類的靜態成員的引用。數組
所以,Java程序在它開始運行以前並不是被徹底加載,其各個部分是在必需時才加載的,C++是靜態加載。安全
類加載器首先檢查這個類的Class對象是否已經加載,若是還沒有加載,默認的類加載器就會根據類名檢查.class文件。這個類的字節碼被加載時,它們會接受驗證以確保其沒有被破壞,而且不包含不良的Java代碼。一旦某個類的Class對象被載入內存,它就被用來建立這個類的全部對象。函數
Class類的靜態方法forName()接受目標類的名字爲參數獲取該類Class對象的引用。若是找不到要加載的類會拋出ClassNotFoundException。不管什麼時候,只要你想在運行時使用類型信息,就必須首先得到恰當的Class對象的引用。Class.forName()是獲取Class對象引用的便捷途徑,由於不須要爲了得到Class引用而持有該類型的對象。可是,若是已經有了一個該對象的引用,能夠經過調用它的getClass()方法來獲取Class對象的引用了,這個方法屬於Object的一部分。第三種生成Class對象引用的方法是類字面常量,例如:工具
FancyToy.class;spa
這樣作不只簡單並且更安全,由於它在編譯時就會受到檢查,所以也不會拋出異常。類字面常量不只能夠應用於普通的類,也能夠應用於接口、數組以及基本數據類型。另外對於基本數據類型的包裝器類還有一個標準字段TYPE。它是指向對應基本數據類型的Class對象,例如:翻譯
boolean.class 與Boolean.TYPE等價。設計
做者建議使用boolean.class來保持與普通類的一致性。
做者提到奇怪的一點是,Class.forName()獲得Class的對象會使得此類進行初始化步驟(此處說的初始化步驟並非指初始化Class的對象,關於使用類而準備的三個步驟見下文),而字面常量**.class獲得Class的對象並不會使此類進行初始化步驟。
爲了使用類而作的準備工做實際包含三個步驟:
(1)加載,由類加載器執行,該步驟查詢字節碼(一般在classpath所指定的路徑中查找),並從這些字節碼中建立一個Class對象;
(2)連接,此階段將驗證類中的字節碼,爲靜態域分配存儲空間,而且若是必須的話,將解析這個類建立的對其餘類的全部引用;
(3)初始化,若是該類具備超類,則對其初始化,執行靜態初始化函數和靜態初始化快。初始化被延遲到了對靜態方法或者很是數靜態域進行首次引用時才執行。
例子的總結:
Class類對象的getName()來產生全限定的類名,getSimpleName()和getCanonicalName()來產生不含包名的類名和全限定的類名。getName()和getCanonicalName()(1.5引入)方法的區別,前者返回的是虛擬機裏面的class表示,後者返回的是更容易理解的表示,好比:
byte[]類型,前者[B,後者是byte[];
byte[][]類型,前者是[[B,後者是byte[][]。
isInterface()方法判斷這個Class對象是不是一個藉口。getInterfaces()方法返回的是該對象實現的接口。getSuperclass()查詢Class對象的基類。newInstance()方法獲得該類的一個新對象,使用它來建立對象必須帶有默認的構造函數。
練習10讓咱們編譯一個程序,使它能過判斷char數組是基本類型仍是一個對象。
char c = new char[3]; System.out.println(c.getClass().getCanonicalName()); System.out.println(c.getClass().getSuperclass().getCanonicalName());
輸出顯示c的基類是Object,說明他是一個對象。
從1.5開始爲Class添加了泛型化處理,若是事先不知道Class的具體類型,能夠寫Class,也能夠寫Class表示它就是一個Number的子類,具體類型暫時不詳。
向Class引用添加泛型語法的緣由是爲了提供編譯期類型檢查。
對類FancyToy的Class對象的getSuperClass()方法僅僅返回的是Class的對象,? super FancyToy表示某個類,它是FancyToy超類。
在1.5中,Class添加了cast()方法,該方法接受對象,並將其轉型爲Class引用的類型。
14.3
P325代碼後面:「抽象的getTypes()方法在導出類中實現」和本頁最後一段「getTypes()方法一般只返回對一個靜態List的引用」,方法名getTypes()錯了應爲types()。
Class的newInstance()方法須要處理兩個異常:
Class
表示一個抽象類、接口、數組類、基本類型或 void; 或者該類沒有空構造方法; 或者因爲其餘某種緣由致使實例化失敗。instanceof有比較嚴格的限制,只能將對象與類的名字進行比較,而不能與Class對象作比較。
做者認爲若是程序中編寫了許多的instanceof表達式,說明程序的設計存在瑕疵。
Class對象的isInstance()方法與instanceof表達式的做用相同。
做者在本節最後的例子中使用了Class對象的isAssignableFrom(Class)方法,斷定此 Class
對象所表示的類或接口與指定的 Class
參數所表示的類或接口是否相同,或是不是其超類或超接口。
14.5
用instanceof和Class.isInstance方法能夠完美的判斷類的繼承的狀況,而用==或者equals()比較兩個Class對象,則不能正確的判斷繼承的狀況。例如:
class Child extends Parent { } Child c = new Child(); System.out.println(c.getClass == Child.class); System.out.ptintln(c.getClass == Parent.class); System.out.println(c instanceof Child); System.out.println(c instanceof Parent);
輸出的結果爲
true
false
true
true
14.6
Class類與java.lang.reflect包中的類一塊兒對反射進行了支持,該庫包含了Field、Method以及Constructor類(每一個類都實現了Member接口)。
注意getConstructor()和getConstructors()方法只能獲取public的構造函數。
14.7
在任什麼時候刻,只要想要將額外的操做從「實際」對象中分離到不一樣的地方,特別是當洗完容易作出修改,從沒有使用額外操做轉爲使用這些操做,或者反過來時,代理顯得特別有用。
Java的動態代理,能夠動態的建立代理並動態的處理對所代理方法的調用。在動態代理商所作的全部調用都會被重定向到單一的調用處理器上。java.lang.reflect.Proxy的靜態方法newProxyInstance()能夠動態建立代理,這個方法須要獲得一個類加載器(一般能夠從已經被加載的對象中獲取類加載器,而後傳遞給它),一個但願代理實現的接口列表(不是類或者抽象類),以及一個InvocationHandler接口的一個實現(即調用處理器)。動態代理能夠將全部調用重定向到調用處理器,所以一般會向調用處理器的構造函數傳遞一個實際對象的引用,從而使得調用處理器在執行其中介任務時能夠將請求轉發。
InvocationHandler的invoke()方法中第一個參數時代理對象,用來須要時區分請求的資源,可是在許多狀況下並不須要它。
練習21和練習22翻譯有誤,原文寫的是so that it measures method-call times,第一眼我也以爲應該翻譯成調用的次數,可是看答案的代碼應該是調用的時間。
練習中讓咱們System.out.println(proxy),致使了異常,看起來應該是循環調用了,以前書中有一句話:「在invoke()內部,在代理上調用方法時須要格外小心,由於對接口的調用將被重定向爲對代理的調用」。此錯誤應該跟這句話有關,但我並非很理解。
14.8
空對象能夠接受傳遞給它的表明對象的信息,可是將返回表示爲實際上並不存在任何「真是」對象的值。經過這種方法,能夠假設全部的對象都是有效的,而沒必要浪費編程精力去檢查Null。
爲了建立空對象,最簡單的方式使建立一個標記接口:
public interface Null { }
這使得instanceof能夠探測空對象,而並不要求在全部的類中都添加isNull()方法。
一般能夠將空對象設置爲單例,這樣就可使用equals()方法和==來判斷是不是空對象。若是使用接口取代具體的空類,還可使用動態代理機制來自動建立空對象。
14.9
做者經過幾個例子向咱們展現了Java的反射機制破壞了封裝特性,經過反射你能夠接觸到普通類、包訪問權限的類、私有內部類、匿名內部類的任何訪問權限的域和方法,可是有一個特殊的地方,final域在遭遇反射修改時是安全的,運行時系統會在不拋出異常的狀況下接受任何修改嘗試,可是實際上不會發生任何修改。
14.10
做者在總結中提到他的一個編程思想:不要太早的關注程序的效率問題,最好首先讓程序運做起來,而後再考慮它的效率。
若是要解決解決效率問題可使用profiler工具。