摘要:接口與類型信息關(guān)鍵字的一種重要目標(biāo)就是允許程序員隔離構(gòu)件,進(jìn)而降低耦合性。如果你編寫接口,那么就可以實(shí)現(xiàn)這一目標(biāo),但是通過類型信息,這種耦合性還是會(huì)傳播出去接口并非是對(duì)解耦的一種無懈可擊的保障。
點(diǎn)擊進(jìn)入我的博客
運(yùn)行時(shí)類型信息使得你可以在運(yùn)行時(shí)發(fā)現(xiàn)和使用類型信息,主要有兩種方式:
“傳統(tǒng)的”RTTI,它假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類型;
“反射”機(jī)制,它允許我們?cè)谶\(yùn)行時(shí)發(fā)現(xiàn)和使用類的信息。
14.1 為什么需要RTTIRTTI維護(hù)類型類型的信息,為多態(tài)機(jī)制的實(shí)現(xiàn)提供基礎(chǔ)。
14.2 Class對(duì)象類型信息在運(yùn)行時(shí)是通過Class對(duì)象來表示的,完成的Class對(duì)象包含了與類有關(guān)的信息。Class對(duì)象就是用來創(chuàng)建所有“常規(guī)”對(duì)象的,Java使用Class對(duì)象來執(zhí)行RTTI
類是程序的一部分,每個(gè)類都有一個(gè)Class對(duì)象,被保存在一個(gè)同名的.class文件中。
類加載器子系統(tǒng)實(shí)際上可以包含一條類加載器鏈,但是只有一個(gè)原生類加載器,它是JVM實(shí)現(xiàn)的一部分。原生類加載器加載的是可信類,包括Java API類。
所有類都是在對(duì)其第一次使用(靜態(tài)成員或new對(duì)象)時(shí),動(dòng)態(tài)加載到JVM的。
Class對(duì)象僅在需要的時(shí)候才會(huì)加載,static初始化是在類加載時(shí)進(jìn)行的。
類加載器首先會(huì)檢查這個(gè)類的Class對(duì)象是否已被加載過,如果尚未加載,默認(rèn)的類加載器就會(huì)根據(jù)類名查找對(duì)應(yīng)的.class文件。
想在運(yùn)行時(shí)使用類型信息,必須獲取對(duì)象的Class對(duì)象的引用:Class.forName("s2.A");。該方法會(huì)自動(dòng)初始化該Class對(duì)象,注意必須使用全限定名(包含包名)。
// 獲取類名 clz.getSimpleName() // 獲取全限定名 clz.getCanonicalName() // 獲取接口 clz.getInterfaces(); // 獲取父類 clz.getSuperClass(); // 創(chuàng)建該類對(duì)象 clz.newInstance();14.2.1 類字面常量
Java還提供了類字面常量的方式來生成對(duì)Class對(duì)象的引用:Class clz = A.class。注意這種方式不會(huì)自動(dòng)初始化該Class對(duì)象。
類字面常量不僅可以用于普通的類,還可以用于接口、數(shù)組(int[].class)和基本數(shù)據(jù)類型(int.class)。
基本類型的包裝類,都有一個(gè)標(biāo)準(zhǔn)字段TYPE,這是一個(gè)指向?qū)?yīng)基本數(shù)據(jù)類型Class對(duì)象的引用:如public static final Class
加載:由類加載器完成,該步驟查找對(duì)應(yīng)的字節(jié)碼,創(chuàng)建一個(gè)Class對(duì)象
鏈接:驗(yàn)證類中的字節(jié)碼,為靜態(tài)域分配空間;并且如果必須的話,將解析這個(gè)類創(chuàng)建的對(duì)其他類的所有引用。
初始化:如果該類有超類,則對(duì)其初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化塊
使用Class.forName()會(huì)自動(dòng)初始化;使用A.class不會(huì)自動(dòng)初始化類
編譯期常量:static final int i = 1;的值,則不需要初始化就可以被讀取。
如果只是將一個(gè)域設(shè)置為static final不足以保證是編譯器常量,如static final int ii = new Random().nextInt();。
如果一個(gè)static域不是final的,那么訪問之前要先進(jìn)行鏈接和初始化。
14.2.2 泛化的Class引用Java SE5之后,Class也可以支持范型了。
向Class引用添加范型語法的原因僅僅是為了提供編譯期類型檢查。
cast()方法接受參數(shù)對(duì)象,將其轉(zhuǎn)型為Class引用的類型。
Class.asSubclass(),該方法允許你講一個(gè)類對(duì)象轉(zhuǎn)型為更加具體的類型。
Class14.3 類型轉(zhuǎn)換前先做檢查clz = String.class; String str1 = clz.cast("");
Java提供類instanceOf關(guān)鍵字,可以判斷對(duì)象是否是某個(gè)類(及其父類)的實(shí)例。
clz.isInstance()方法接受一個(gè)對(duì)象,判斷該對(duì)象是否是該clz指向的類的實(shí)例。
clz.isAssignableFrom()方法接受一個(gè)Class對(duì)象,判斷該Class對(duì)象是否是clz自身或子類。
public class Test { public static void main(String[] args) throws Exception { System.out.println(A.class.isAssignableFrom(C.class)); System.out.println(B.class.isAssignableFrom(C.class)); System.out.println(A.class.isInstance(new C())); System.out.println(B.class.isInstance(new C())); } } class A { } interface B {} class C extends A implements B {} // Output: // true // true // true // true14.4 注冊(cè)工廠
使用工廠方法設(shè)計(jì)模式, 將對(duì)象的創(chuàng)建工作交給類自己去完成。 工廠方法可以被多態(tài)地調(diào)用, 從而為你創(chuàng)建恰當(dāng)類型的對(duì)象。
14.5 instanceOf和Class的等價(jià)性instanceOf和isInstance()的結(jié)果完全一樣,比較的時(shí)候都考慮了繼承關(guān)系
A.class.equals(B.class) 和 A.class == B.class 只能比較是否為同一個(gè)類,沒有考慮繼承關(guān)系
14.6 反射:運(yùn)行時(shí)的類信息如果不知道某個(gè)對(duì)象的確切類型,RTTI可以告訴你,但這有個(gè)限制:這個(gè)類型在編譯時(shí)必須已知。換句話說,編譯器在編譯時(shí)必須知道所有要通過RTTI來處理的類。
假設(shè)你獲取了一個(gè)指向某個(gè)并不在你程序空間中對(duì)象的引用,在編譯時(shí)你的程序根本無法獲知這個(gè)對(duì)象所屬的類。
運(yùn)行時(shí)獲取類的信息場景:基于構(gòu)件的編程、遠(yuǎn)程方法調(diào)用(RMI)。
當(dāng)通過反射與一個(gè)未知類型的對(duì)象打交道時(shí),JVM只是簡單地檢查這個(gè)對(duì)象,看它屬于哪個(gè)特定的類(就像RTTI那樣)。
在用它做其他事情之前必須先加載那個(gè)類的Class對(duì)象。因此,那個(gè)類的.class文件對(duì)于JVM來說必須是可獲取的:要么在本地機(jī)器上,要么可以通過網(wǎng)絡(luò)取得。
所以RTTI和反射之間真正的區(qū)別只在于,對(duì)RTTI來說,編譯器在編譯時(shí)打開和檢查.class文件;而對(duì)于反射機(jī)制來說,.class文件在編譯時(shí)是不可獲取的,所以是在運(yùn)行時(shí)打開和檢查class文件。
反射在Java中是用來支持其他特性的,例如對(duì)象序列化和JavaBean。
14.7 動(dòng)態(tài)代理代理是基本的設(shè)計(jì)模式之一,它是為你提供額外的或者不同的操作,而插入的用來代替“實(shí)際”對(duì)象的對(duì)象。這些操作通常設(shè)計(jì)與“實(shí)際”對(duì)象的通信,因此代理通常充當(dāng)著中間人的角色。
靜態(tài)代理就是寫死了在代理對(duì)象中執(zhí)行這個(gè)方法前后執(zhí)行添加功能的形式。
優(yōu)點(diǎn):可以做到在符合開閉原則的情況下對(duì)目標(biāo)對(duì)象進(jìn)行功能擴(kuò)展。
缺點(diǎn):我們得為每一個(gè)服務(wù)都得創(chuàng)建代理類,工作量太大,不易管理。同時(shí)接口一旦發(fā)生改變,代理類也得相應(yīng)修改。
public class Test { public static void main(String[] args) throws Exception { new RealObject().doSomething(); System.out.println("代理之后:"); new SimpleProxy(new RealObject()).doSomething(); } } interface MyInterface { void doSomething(); } class RealObject implements MyInterface { @Override public void doSomething() { System.out.println("RealObject"); } } class SimpleProxy implements MyInterface { private MyInterface myInterface; public SimpleProxy(MyInterface myInterface) { this.myInterface = myInterface; } // 代理后增加方法 @Override public void doSomething() { System.out.println("SimpleProxy"); myInterface.doSomething(); } }
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)用處理器上。
通過Proxy.newProxyInstance()可以創(chuàng)建動(dòng)態(tài)代理,需要一個(gè)類加載器(通常是被加載的對(duì)象獲取)、一個(gè)希望實(shí)現(xiàn)的接口列表(不是類或抽象類)、以及InvocationHandler的一個(gè)實(shí)現(xiàn)。
動(dòng)態(tài)代理可以將所有對(duì)接口的調(diào)用重定向?yàn)閷?duì)代理的調(diào)用。
使用動(dòng)態(tài)代理來編寫一個(gè)系統(tǒng)以實(shí)現(xiàn)事務(wù),其中,代理在被代理的調(diào)用執(zhí)行成功(不拋出任何異常)執(zhí)行提交,而在執(zhí)行失敗時(shí)執(zhí)行回滾。你的提交和回滾都針對(duì)一個(gè)外部的文本文件,該文件不在Java異常的控制范圍之內(nèi)。你必須注意操作的原子性。
MyInterface myInterface = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(), new Class[]{MyInterface.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理方法"); return method.invoke(new RealObject(), args); } }); myInterface.doSomething();14.8 空對(duì)象
使用null的時(shí)候每次都要檢查是否為null,這是一件很麻煩的事。
引人空對(duì)象的思想將會(huì)很有用,它可以接受傳遞給它的所代表的對(duì)象的消息,但是將返回表示為實(shí)際上并不存在任何“真實(shí)”對(duì)象的值。通過這種方式,你可以假設(shè)所有的對(duì)象都是有效的,而不必浪費(fèi)編程精力去檢查null。
通常空對(duì)象是單例的,所以你不僅可以用instanceOf來比較,還可以用equals或==來比較。
注意:在某些地方仍然必須測試空對(duì)象,這與檢查是否為null沒有區(qū)別,但在很多地方就不必執(zhí)行額外的測試了,可以直接假設(shè)所有對(duì)象都是有效的。
public class Test { public static void main(String[] args) throws Exception { // 在使用的時(shí)候可以直接使用而不會(huì)報(bào)錯(cuò)空指針 Person p = Person.NULL_PERSON; System.out.println(p.toString()); } } // 空標(biāo)記接口 interface Null {} class Person { void func() { System.out.println("Person"); } // 空對(duì)象 private static class NullPerson extends Person implements Null { private NullPerson() {} @Override public String toString() { return "NullPerson"; } } public static final Person NULL_PERSON = new NullPerson(); }
假設(shè)有不同的多個(gè)Person的子類,我們相對(duì)每一個(gè)都創(chuàng)建一個(gè)空對(duì)象。無論何時(shí),如果你需要一個(gè)空Person對(duì)象,只需要調(diào)用newNullPerson()并傳遞需要代理的Person的類型。
public class Test { public static Person newNullPerson(Class extends Person> type) { return (Person) Proxy.newProxyInstance(NullPerson.NULL_PERSON.getClass().getClassLoader(), new Class[]{type}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理"); return method.invoke(NullPerson.NULL_PERSON, args); } }); } public static void main(String[] args) throws Exception { Person p = newNullPerson(Person.class); p.func(); } } // 空標(biāo)記接口 interface Null {} // 父接口 interface Person { void func(); } // 空Person class NullPerson implements Person, Null { @Override public void func() { System.out.println("NullPerson"); } public static final Person NULL_PERSON = new NullPerson(); private NullPerson() {} }14.8.1 模擬對(duì)象與樁
空對(duì)象的邏輯變體是模擬對(duì)象和樁。與空對(duì)象一樣,它們都表示在最終的程序中所使用的“實(shí)際”對(duì)象。但是,模擬對(duì)象和樁都只是假扮可以傳遞實(shí)際信息的存活對(duì)象,而不是像空對(duì)象那樣可以成為null的一種更加智能化的替代物。
模擬對(duì)象和樁之間的差異在于程度不同。模擬對(duì)象往往是輕量級(jí)和自測試的,通常很多模擬對(duì)象被創(chuàng)建出來是為了處理各種不同的測試情況。樁只是返回樁數(shù)據(jù),它通常是重量級(jí)的,并且經(jīng)常在測試之間被復(fù)用。樁可以根據(jù)它們被調(diào)用的方式,通過配置進(jìn)行修改,因此樁是一 種復(fù)雜對(duì)象,它要做很多事。然而對(duì)于模擬對(duì)象,如果你需要做很多事情,通常會(huì)創(chuàng)建大量小而簡單的模擬對(duì)象。
14.9 接口與類型信息interface關(guān)鍵字的一種重要目標(biāo)就是允許程序員隔離構(gòu)件,進(jìn)而降低耦合性。如果你編寫接口,那么就可以實(shí)現(xiàn)這一目標(biāo),但是通過類型信息,這種耦合性還是會(huì)傳播出去——接口并非是對(duì)解耦的一種無懈可擊的保障。
public class Test { public static void main(String[] args) { A a = new B(); a.a(); // 我們需要的是用戶使用接口,但是強(qiáng)制轉(zhuǎn)型還是可以訪問不存在于接口中的方法 ((B) a).b(); } } interface A { void a(); } class B implements A { @Override public void a() {} public void b() {} }
如果程序員不使用接口而是子類,它們要對(duì)自己負(fù)責(zé)。即B a = new B();代替A a = new B();。
此時(shí)在此包外只能使用Hidden.newA()來獲取對(duì)象,而且由于沒有B類的信息,也無法強(qiáng)制轉(zhuǎn)型。
class B implements A { @Override public void a() {} public void b() {} } public class HiddenB { public static A newA() { return new B(); } }
通過使用反射,仍舊可以到達(dá)并調(diào)用所有方法,甚至是private方法!如果知道方法名,你就可以在其Method對(duì)象上調(diào)用setAccessible(true)。
final域?qū)嶋H上在遭遇修改時(shí)是安全的。運(yùn)行時(shí)系統(tǒng)會(huì)在不拋異常的情況下接受任何修改嘗試,但是實(shí)際上不會(huì)發(fā)生任何修改。
14.10 總結(jié)RTTI允許通過匿名基類的引用來發(fā)現(xiàn)類型信息。
面向?qū)ο缶幊陶Z言的目的是讓我們?cè)诜彩强梢允褂玫牡胤蕉际褂枚鄳B(tài)機(jī)制,只在必需的時(shí)候使用RTTI。
可繼承一個(gè)新類,然后添加你需要的方法。在代碼的其他地方,可以檢査你自己特定的類型,并調(diào)用你自己的方法,這樣做不會(huì)破壞多態(tài)性以及程序的擴(kuò)展能力。
但如果在程序主體中添加需要的新特性的代碼,就必須使用RTTI來檢査你的特定的類型。
一致的錯(cuò)誤報(bào)告模型的存在使我們能夠通過使用反射編寫動(dòng)態(tài)代碼。當(dāng)然,盡力編寫能夠進(jìn)行靜態(tài)檢査的代碼是值得的,只要你確實(shí)能夠這么做。但是我相信動(dòng)態(tài)代碼是將Java與其他例如C++這樣的語言區(qū)分開的重要工具之一。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/72201.html
摘要:自動(dòng)拆箱用賦值運(yùn)算符把一個(gè)包裝類賦值給一個(gè)基本類型變量,或者是在包裝類進(jìn)行數(shù)值運(yùn)算時(shí)。指數(shù)計(jì)數(shù),表示的冪按位操作符可以把值看成單比特值對(duì)待,的操作相同,但是不能用于布爾值。移位操作符高位包括符號(hào)位舍棄,低位補(bǔ)零。 點(diǎn)擊進(jìn)入我的博客 3.1更簡單的打印語句 System.out.println(imbug); 通過編寫一個(gè)小類庫,并通過import static該方法來實(shí)現(xiàn)簡化打印(基...
摘要:方法的基本組成包括名稱參數(shù)返回值方法體方法名和參數(shù)列表唯一的標(biāo)識(shí)出某個(gè)方法。如果返回的類型是,則的作用僅是退出方法否則必須返回正確的返回值包名名字可見性約定以域名反轉(zhuǎn)作為包名,用來劃分子目錄,并且全部小寫。 點(diǎn)擊進(jìn)入我的博客 2.1用引用操縱對(duì)象 盡管一切都看作對(duì)象,但操縱的標(biāo)識(shí)符實(shí)際上是對(duì)象的一個(gè)引用。 String s; // s是一個(gè)String類型的引用, 并沒有任何對(duì)象與其...
摘要:在初始化和步進(jìn)控制部分,可以用一系列由逗號(hào)分割的語句,而且那些語句會(huì)獨(dú)立執(zhí)行。和都表示無限循環(huán)語法數(shù)組等關(guān)鍵詞有兩個(gè)方面的用途一方面指定一個(gè)方法返回什么值另一個(gè)方面指定當(dāng)前的方法退出,并返回那個(gè)值。 點(diǎn)擊進(jìn)入我的博客 4.1 true&false Java的條件語句只能使用布爾值來決定執(zhí)行路徑 4.2 if-else 4.3 循環(huán)語句 while、for、do-while do-...
摘要:一引用操縱對(duì)象在的世界里,一切都被視為對(duì)象。特點(diǎn)創(chuàng)建程序時(shí),需要知道存儲(chǔ)在棧內(nèi)所有數(shù)據(jù)的確切生命周期,以便上下移動(dòng)堆棧指針。因?yàn)椋赶蛲粔K內(nèi)存空間除了通過對(duì)象引用靜態(tài)變量,我們還可以通過類直接引用靜態(tài)變量 一、引用操縱對(duì)象 在Java的世界里,一切都被視為對(duì)象。操縱的標(biāo)識(shí)符實(shí)際上是對(duì)象的引用, 例如:遙控器與電視的關(guān)系。 可以在沒有對(duì)象關(guān)聯(lián)的情況下,擁有一個(gè)引用。沒有電視機(jī),也可以擁...
摘要:多態(tài)的作用是消除類型之間的耦合關(guān)系。編寫構(gòu)造器準(zhǔn)則用盡可能簡單的方法使對(duì)象進(jìn)入正常狀態(tài),如果可以的話,避免調(diào)用其他方法。 點(diǎn)擊進(jìn)入我的博客 在面向?qū)ο蟮某绦蛟O(shè)計(jì)語言中,多態(tài)是繼數(shù)據(jù)抽象(封裝)和繼承之后的第三種基本特征。多態(tài)通過分離做什么和怎么做,從另一角度將接口和實(shí)現(xiàn)分離開來。多態(tài)的作用是消除類型之間的耦合關(guān)系。 8.1 再論向上轉(zhuǎn)型 對(duì)象既可以作為它自己的本類使用,也可以作為它的...
閱讀 2035·2023-04-25 14:50
閱讀 2914·2021-11-17 09:33
閱讀 2618·2019-08-30 13:07
閱讀 2845·2019-08-29 16:57
閱讀 913·2019-08-29 15:26
閱讀 3555·2019-08-29 13:08
閱讀 1996·2019-08-29 12:32
閱讀 3391·2019-08-26 13:57