《JAVA編程思想》學(xué)習(xí)筆記:第14章(類型信息)

第十四章、類型信息

14.1 RTTI

RTTI:(Runtime Type Identification)運(yùn)行階段類型識(shí)別

在Java中,所有的類型轉(zhuǎn)換都是在運(yùn)行時(shí)進(jìn)行正確性檢查的。這也是RTTI的含義:在運(yùn)行時(shí),識(shí)別一個(gè)對(duì)象的類型。

14.1.1 丟失具體類型信息的問題

多態(tài)中表現(xiàn)的類型轉(zhuǎn)換:是RTTI最基本的使用形式,但這種轉(zhuǎn)換并不徹底(多態(tài)≠RTTI)。

舉例:如數(shù)組容器實(shí)際上將所有元素當(dāng)作Object持有,取用時(shí)再自動(dòng)將結(jié)果轉(zhuǎn)型回聲明類型。而數(shù)組在填充(持有)對(duì)象時(shí),具體類型可能是聲明類型的子類,這樣放到數(shù)組里就會(huì)向上轉(zhuǎn)型為聲明類型,持有的對(duì)象就丟失了具體類型。而取用時(shí)將由Object只轉(zhuǎn)型回聲明類型,并不是具體的子類類型,所以這種轉(zhuǎn)型并不徹底。

多態(tài)中表現(xiàn)了具體類型的行為,但那只是“多態(tài)機(jī)制”的事情,是由引用所指向的具體對(duì)象而決定的,并不等價(jià)于在運(yùn)行時(shí)識(shí)別具體類型。

14.2 Class對(duì)象

14.2.1 RTTI在Java中的工作原理

要能夠在運(yùn)行時(shí)識(shí)別具體類型,說明必然有東西在運(yùn)行時(shí)保存了具體類型信息,這個(gè)東西就是Class對(duì)象。

Class對(duì)象:一種特殊對(duì)象。即Class對(duì)象表示了運(yùn)行時(shí)的類型信息,它包含了與類有關(guān)的信息。

a. 事實(shí)上Class對(duì)象就是用來創(chuàng)建類的所有的“常規(guī)”對(duì)象的。

b. 每個(gè)類都有一個(gè)Class對(duì)象。換言之,每當(dāng)編寫并且編譯了一個(gè)新類,就會(huì)產(chǎn)生一個(gè)Class對(duì)象(更恰當(dāng)?shù)卣f,是被保存在一個(gè)同名的.class文件中)。

c. Class對(duì)象在**.java文件編譯成**.class文件時(shí)就生成了,且就保存在這個(gè).class文件中。

14.2.2 Class對(duì)象用來生成對(duì)象(常規(guī)對(duì)象,非Class對(duì)象)

運(yùn)行程序的JVM使用所謂的“類加載器”的子系統(tǒng)(class loader subsystem)通過加載Class對(duì)象(或者說.class文件)來生成一個(gè)類的對(duì)象。

所有的類都是在對(duì)其第一次使用時(shí),動(dòng)態(tài)加載到JVM中的。當(dāng)程序第一次使用類的靜態(tài)成員時(shí),就會(huì)加載這個(gè)類,這說明構(gòu)造器也是靜態(tài)方法,即使構(gòu)造器前面沒加static關(guān)鍵字。

因此,Java程序在它開始運(yùn)行之前并非被完全加載,其各個(gè)部分是在必須時(shí)才被加載的。(C++這種靜態(tài)加載語言是很難做到的。)

14.2.3 類加載器的工作(過程)

step1: 首先檢查一個(gè)類的Class對(duì)象(或理解.class文件)是否已被加載;

step3: 一旦Class對(duì)象(.class文件)被加載了(載入內(nèi)存),它就被用來創(chuàng)建這個(gè)類的所有對(duì)象。

其中:

a. static子句在類第一次被加載時(shí)執(zhí)行。

b. Class對(duì)象僅在需要時(shí)才被加載,

c. static初始化是在類加載時(shí)進(jìn)行的。

14.2.4 獲得Class對(duì)象引用的方法(兩種)

方法1:Class.forName()

Class.forName(net.mrliuli.rtti.Gum)是Class類的一個(gè)靜態(tài)成員,用來返回一個(gè)Class對(duì)象的引用(Class對(duì)象和其他對(duì)象一樣,我們可以獲取并操作它的引用(這也就是類加載器的工作))。使用這個(gè)方法時(shí),如果net.mrliuli.rtti.Gum還沒有被加載就加載它。在加載過程中,Gum的static子句被執(zhí)行。

總之,無論何時(shí),只要你想在運(yùn)行時(shí)使用類型信息,就必須首先獲得對(duì)恰當(dāng)?shù)腃lass對(duì)象的引用。

通過Class.forName(),就是一個(gè)便捷途徑,這種方式不需要為了獲得Class對(duì)象引用而持有該類型的對(duì)象。(即沒有創(chuàng)建過或沒有這個(gè)類型的對(duì)象的時(shí)候就可以獲得Class對(duì)象引用。)

方法2:已創(chuàng)建對(duì)象的Object.getClass()

如果已經(jīng)有一個(gè)類型的對(duì)象,那就可以通過調(diào)用這個(gè)對(duì)象的getClass()方法來獲取它的Class對(duì)象引用了。這個(gè)方法屬于Object,返回表示該對(duì)象的實(shí)際類型的Class對(duì)象引用。

方法3:類字面常量.class,如 FancyToy.class。建議使用這種方法。

14.2.5 Class包含的有用的方法

getName() 獲取類的全限定名稱

getSimpleName() 獲取不含包名的類名

getCanonicalName() 獲取全限定的類名

isInterface() 判斷某個(gè)Class對(duì)象是否是接口

getInterfaces() 返回Class對(duì)象實(shí)現(xiàn)的接口數(shù)組

getSuperClass() 返回Class對(duì)象的直接基類

其中:newInstance()這個(gè)方法依賴于Class對(duì)象所代表的類必須具有可訪問的默認(rèn)的構(gòu)造函數(shù)(Nullary constructor,即無參的構(gòu)造器),否則會(huì)拋出InstantiationException 或 IllegalAccessException 異常。

備注:Class引用在編譯期不具備任何更進(jìn)一步的類型信息,所以它返回的只是一個(gè)Object引用,但是這個(gè)Object引用指向的是這個(gè)Class引用所代表的具體類型。即需要轉(zhuǎn)型到具體類型才能給它發(fā)送Object以外的消息。

14.2.6 類字面常量

14.2.6.1 使用類字面常量.class是獲取Class對(duì)象引用的另一種方法。

如 FancyToy.class。建議使用這種方法。

編譯時(shí)就會(huì)受到檢查(因此不需要放到try語句塊中),所以既簡(jiǎn)單又安全。根除了對(duì)forName()的調(diào)用,所以也更高效。

類字面常量.class不僅適用于普通的類,也適用于接口、數(shù)組和基本類型。

注意,使用.class來創(chuàng)建Class對(duì)象的引用時(shí),不會(huì)自動(dòng)地初始化該Class對(duì)象。

14.2.6.2 為了使用類而做的準(zhǔn)備工作實(shí)際包含三個(gè)步驟:

step1: 加載。這是由類加載器執(zhí)行的。該步驟將查找字節(jié)碼(通常在CLASSPATH所指定的路徑中查找),并從這些字節(jié)碼中創(chuàng)建一個(gè)Class對(duì)象。

step2: 鏈接。在鏈接階段將驗(yàn)證類中的字節(jié)碼,為靜態(tài)域分配存儲(chǔ)空間,并且如果必需的話,將解析這個(gè)類創(chuàng)建的對(duì)其他類的所有引用。

step3: 初始化。如果該類具有超類,則對(duì)其初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始?jí)K。

14.2.6.3 初始化惰性

初始化被延遲到了對(duì)靜態(tài)方法(構(gòu)造器隱式地是靜態(tài)的)或者非常數(shù)靜態(tài)域進(jìn)行首次引用時(shí)才執(zhí)行,即初始化有效地實(shí)現(xiàn)了盡可能 的“惰性”。

備注:

非常數(shù)靜態(tài)域:static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);就不是編譯期常量,對(duì)它的引用將強(qiáng)制進(jìn)行類的初始化。

常數(shù)靜態(tài)域: static final int staticFinal = 47;

class Initable{

? ? static final int staticFinal = 47;? ? ? // 常數(shù)靜態(tài)域

? ? static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);? ? // 非常數(shù)靜態(tài)域(不是編譯期常量)

}

class Initable2{

? ? static int staticNonFinal = 147;? ? // 非常數(shù)靜態(tài)域

}

public class ClassInitialization {

? ? public static Random rand = new Random(47);

? ? public static void main(String[] args) throws Exception {

? ? ? ? Class initalbe = Initable.class;? ? ? // 使用類字面常量.class獲取Class對(duì)象引用,不會(huì)初始化

? ? ? ? System.out.println(Initable.staticFinal);? ? ? // 常數(shù)靜態(tài)域首次引用,不會(huì)初始化

? ? ? ? System.out.println(Initable.staticFinal2);? ? ? // 非常數(shù)靜態(tài)域首次引用,會(huì)初始化

? ? ? ? System.out.println(Initable2.staticNonFinal);? // 非常數(shù)靜態(tài)域首次引用,會(huì)初始化

? ? ? ? Class initable3 = Class.forName("net.mrliuli.rtti.Initable3");? ? ? // 使用Class.forName()獲取Class對(duì)象引用,會(huì)初始化

? ? }

}

14.2.7 泛化的Class引用

14.2.7.1 Class對(duì)象類型限制<T>

Class引用總是指向某個(gè)Class對(duì)象,此時(shí),這個(gè)Class對(duì)象可以是各種類型的,當(dāng)使用泛型語法對(duì)Class引用所指向的Class對(duì)象的類型進(jìn)行限定時(shí),這就使得Class對(duì)象的類型變得具體,這樣編譯器編譯時(shí)也會(huì)做一些額外的類型檢查工作。如

public class GenericClassReferences {

? ? public static void main(String[] args){

? ? ? ? Class<Integer> genericIntClass = int.class;

? ? ? ? genericIntClass = Integer.class;? ? // Same thing

? ? }

}

14.2.7.2 使用通配符?放松對(duì)Class對(duì)象類型的限制

通配符<?>:是Java泛型的一部分,?表示“任何事物”。

Class<?> intClass = int.class; 與 Class intClass = int.class; 是等價(jià)的,但使用Class<?>優(yōu)于使用Class,因?yàn)樗f明了你是明確要使用一個(gè)非具體的類引用,才選擇了一個(gè)非具體的版本,而不是由于你的疏忽。

有限通配符:<? extends **> 或者 <? super **>

Class<? extends Number>

Class<? super Number>

將通配符與extends關(guān)鍵字相結(jié)合如Class<? extends Number>,就創(chuàng)建了一個(gè)范圍,使得這個(gè)Class引用被限定為Number類型或其子類型。

14.2.7.5 類型轉(zhuǎn)換前先做檢查

RTTI形式包括:

方式1:傳統(tǒng)類型轉(zhuǎn)換,如(Shape),代表對(duì)象的類型的Class對(duì)象

方式2:關(guān)鍵字 instanceof。它返回一個(gè)布爾值,告訴我們對(duì)象是不是某個(gè)特定類型或其子類。如if(x instanceof Dog)語句會(huì)檢查對(duì)象x是否從屬于Dog類。

方式3:動(dòng)態(tài)的instanceof:Class.isInstance()方法提供了一種動(dòng)態(tài)地測(cè)試對(duì)象的途徑。Class.isInstance()方法使我們不再需要instanceof表達(dá)式。

14.2.7.6 isAssignableFrom()

Class.isAssignableFrom() :調(diào)用類型可以被參數(shù)類型賦值,即判斷傳遞進(jìn)來的參數(shù)是否屬于調(diào)用類型繼承結(jié)構(gòu)(是調(diào)用類型或調(diào)用類型的子類)。

14.3 注冊(cè)工廠

工廠方法(設(shè)計(jì)模式):將對(duì)象的創(chuàng)建工作交給類自己完成。

public interface Factory<T>{

? ? T create();

}

14.4 instanceof 與 Class 的等價(jià)性

instanceof 和 isInstance() 保持了類型的概念,它指的是“你是這個(gè)類嗎,或者你是這個(gè)類的派生類嗎?”

== 和 equals() 沒有考慮繼承——它要么是這個(gè)確切的類型,要么不是。

14.5 反射

運(yùn)行時(shí)的類信息(Reflection: runtime class information)

Class類與 java.lang.reflect類庫一起對(duì)反射的概念進(jìn)行了支持。

RMI: 遠(yuǎn)程方法調(diào)用(Remote Method Invocation):在跨網(wǎng)絡(luò)的遠(yuǎn)程平臺(tái)上創(chuàng)建和運(yùn)行對(duì)象的能力。

RTTI與反射的真正區(qū)別在于:

對(duì)于RTTI來說,是編譯時(shí)打開和檢查.class文件。(換句話說,我們可以用“普通”方式調(diào)用對(duì)象的所有方法。)

對(duì)于反射機(jī)制來說,.class文件在編譯時(shí)是不可獲取的,所以是在運(yùn)行時(shí)打開和檢查.class文件。

14.6 動(dòng)態(tài)代理

Java的動(dòng)態(tài)代理比代理的思想更向前邁進(jìn)了一步,因?yàn)樗梢詣?dòng)態(tài)地創(chuàng)建代理并動(dòng)態(tài)地處理對(duì)所代理方法的調(diào)用。

在動(dòng)態(tài)代理上所做的所有調(diào)用都會(huì)被重定向到單一的調(diào)用處理器上,它的工作是揭示調(diào)用的類型并確定相應(yīng)的對(duì)策。

通過調(diào)用靜態(tài)方法Proxy.newProxyInstance()可以創(chuàng)建動(dòng)態(tài)代理,需要三個(gè)參數(shù):

ClassLoader loader 一個(gè)類加載器,通常可以從已經(jīng)被加載的對(duì)象中獲取其類加載器

Class<?>[] interfaces 一個(gè)希望代理要實(shí)現(xiàn)的接口列表(不是類或抽象類)

InvocationHandler h 一個(gè)調(diào)用處理器接口的實(shí)現(xiàn)

動(dòng)態(tài)代理可以將所有調(diào)用重定向到調(diào)用處理器,因此通常會(huì)向調(diào)用處理器傳遞一個(gè)“實(shí)際”對(duì)象(即被代理的對(duì)象)的引用,從而使得調(diào)用處理器在執(zhí)行其中介任務(wù)時(shí),可以將請(qǐng)求轉(zhuǎn)發(fā)(即去調(diào)用實(shí)際對(duì)象)。

14.6.1 動(dòng)態(tài)代理的優(yōu)點(diǎn)及美中不足

優(yōu)點(diǎn):動(dòng)態(tài)代理與靜態(tài)代理相較,最大的好處是接口中聲明的所有方法都被轉(zhuǎn)移到調(diào)用處理器一個(gè)集中的方法(InvocationHandler.invoke)中處理。這樣,在接口方法數(shù)量比較多的時(shí)候,我們可以進(jìn)行靈活處理,而不需要像靜態(tài)代理那樣每一個(gè)方法進(jìn)行中轉(zhuǎn)。

美中不足:它始終無法擺脫僅支持interface代理的桎梏,因?yàn)樗脑O(shè)計(jì)注定了這個(gè)遺憾。

14.7. 空對(duì)象

eg:

14.7.2 模擬對(duì)象與樁(Mock Objects & Stubs)

空對(duì)象的邏輯變體是模擬對(duì)象和樁。

14.8. 接口與類型信息

通過使用反射,可以到達(dá)并調(diào)用一個(gè)類的所有方法,包括私有方法!如果知道方法名,就可以在其Method對(duì)象上調(diào)用setAccessible(true),然后訪問私有方法。

class A {

? ? private void g(){

? ? ? ? System.out.println("B.g()");

? ? }

}

/**

* 通過反射調(diào)用所有方法(包括私有的)

*/

public class HiddenImplementation {

? ? static void callHiddenMethod(Object obj, String methodName, Object[] args) throws Exception{

? ? ? ? Method method = obj.getClass().getDeclaredMethod(methodName);

? ? ? ? method.setAccessible(true);

? ? ? ? method.invoke(obj, args);

? ? }

? ? public static void main(String[] args) throws Exception{

? ? ? ? callHiddenMethod(new A(), "g", null);

? ? }

}

14.9 總結(jié)

多態(tài):通過運(yùn)行時(shí)動(dòng)態(tài)綁定, 動(dòng)態(tài)判定當(dāng)前對(duì)象引用的類型, 調(diào)用本類對(duì)應(yīng)的函數(shù);如果沒有,則調(diào)用父類的函數(shù)。

RTTI:instanceOf/ isInstance() :JAVA RTTI的核心是.Class,Class對(duì)象存放著對(duì)應(yīng)類所需要的所有的類型信息,包括類的變量,類的屬性,類的超類,類實(shí)現(xiàn)的借口,類的修飾符,類的對(duì)應(yīng)的類的加載器等等。

反射:是在運(yùn)行狀態(tài)時(shí),對(duì)于任意的一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)任意一個(gè)對(duì)象都能夠通過反射機(jī)制調(diào)用一個(gè)類的任意方法,這種動(dòng)態(tài)獲取類信息及動(dòng)態(tài)調(diào)用類對(duì)象方法的功能稱為java的反射機(jī)制。

動(dòng)態(tài)代理:Java的動(dòng)態(tài)代理比代理的思想更前進(jìn)了一步,它可以動(dòng)態(tài)地創(chuàng)建并代理并動(dòng)態(tài)地處理對(duì)所代理方法的調(diào)用。在動(dòng)態(tài)代理上所做的所有調(diào)用都會(huì)被重定向到單一的調(diào)用處理器上,它的工作是揭示調(diào)用的類型并確定相應(yīng)的策略。

Spring主要有兩大思想:一個(gè)是IoC,另一個(gè)就是AOP。對(duì)于IoC,它利用的是反射機(jī)制,依賴注入就不用多說了;而對(duì)于Spring的核心AOP來說,使用了動(dòng)態(tài)代理,其實(shí)底層也是反射。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一年又一年,字節(jié)跳動(dòng) Lark(飛書) 研發(fā)團(tuán)隊(duì)又雙叒叕開始招新生啦!【內(nèi)推碼】:GTPUVBA【內(nèi)推鏈接】:ht...
    盧卡斯嗶嗶嗶閱讀 529評(píng)論 0 1
  • 第 14 章 類型信息 運(yùn)行時(shí)類型信息使得你可以在程序運(yùn)行時(shí)發(fā)現(xiàn)和使用類型信息 Java 在運(yùn)行時(shí)識(shí)別對(duì)象和類有兩...
    智勇雙全的小六閱讀 240評(píng)論 0 0
  • 第一章 對(duì)象導(dǎo)論 對(duì)象具有狀態(tài)、行為和標(biāo)識(shí)。這意味著每一個(gè)對(duì)象都可以擁有內(nèi)部數(shù)據(jù)和方法,并且每一個(gè)對(duì)象都可以唯一地...
    niaoge2016閱讀 1,037評(píng)論 0 0
  • 1. Java中的多態(tài)性理解(注意與C++區(qū)分) Java中除了static方法和final方法(private方...
    小敏紙閱讀 1,530評(píng)論 0 19
  • Thinking in java 讀書筆記系列 運(yùn)行時(shí)類型信息使得你可以在程序運(yùn)行時(shí)發(fā)現(xiàn)和使用類型信息。Java ...
    下位子閱讀 366評(píng)論 3 2

友情鏈接更多精彩內(nèi)容