您好,登錄后才能下訂單哦!
這篇文章給大家介紹反射機(jī)制在Java中的作用有哪些,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
RTTI(RunTime Type Information)運(yùn)行時(shí)類型信息,能夠在程序運(yùn)行時(shí)發(fā)現(xiàn)和使用類型信息,把我們從只能在編譯期知曉類型信息并操作的局限中解脫出來(lái)
傳統(tǒng)的多態(tài)機(jī)制正是 RTTI 的基本使用:假設(shè)有一個(gè)基類 Shape 和它的三個(gè)子類 Circle、Square、Triangle,現(xiàn)在要把 Circle、Square、Triangle 對(duì)象放入 List<Shape> 中,在運(yùn)行時(shí),先把放入其中的所有對(duì)象都當(dāng)作 Object 對(duì)象來(lái)處理,再自動(dòng)將類型轉(zhuǎn)換為 Shape。所有類型轉(zhuǎn)換的正確性檢查都是在運(yùn)行時(shí)進(jìn)行的,這也正是 RTTI 的含義所在:在運(yùn)行時(shí),識(shí)別一個(gè)對(duì)象的類型
但這樣的類型轉(zhuǎn)換并不徹底,Object 只是被轉(zhuǎn)型為 Shape,而不是更具體的 Circle、Square、Triangle,如果我們希望得到更具體的類型呢?比如說(shuō)我們現(xiàn)在需要旋轉(zhuǎn)所有圖形,但是想跳過(guò)圓形(圓形旋轉(zhuǎn)沒(méi)有意義),這時(shí)可以使用 RTTI 查詢某個(gè) Shape 引用所指向?qū)ο蟮拇_切類型,然后選擇進(jìn)行合適的處理
眾所周知,每當(dāng)我們編寫并編譯了一個(gè)新類,就會(huì)產(chǎn)生一個(gè) Class 對(duì)象,它包含了與類有關(guān)的信息。我們可以使用 Class 對(duì)象來(lái)實(shí)現(xiàn) RTTI,一旦某個(gè)類的 Class 對(duì)象被載入內(nèi)存,它就可以用來(lái)創(chuàng)建這個(gè)類的所有對(duì)象
Class 對(duì)象都屬于 Class 類型,既然它也是對(duì)象,那我們就可以獲取和操控它的引用。forName() 是 Class 類的一個(gè)靜態(tài)方法,我們可以使用 forName() 根據(jù)目標(biāo)類的全限定名(包含包名)得到該類的 Class 對(duì)象。使用 forName() 會(huì)有一個(gè)副作用,那就是如果這個(gè)類沒(méi)有被加載就會(huì)加載它,而在加載的過(guò)程中,Gum 類的 static 初始?jí)K會(huì)被執(zhí)行。當(dāng) Class.forName() 找不到要加載的類,就會(huì)拋出異常
ClassNotFoundException
Class gumClass = Class.forName("Gum");
使用 Class.forName() 你不需要先持有這個(gè)類型的對(duì)象,但如果你已經(jīng)擁有了目標(biāo)類的對(duì)象,那就可以通過(guò)調(diào)用 getClass() 方法來(lái)獲取 Class 引用,這個(gè)方法來(lái)自根類 Object,它將返回表示該對(duì)象實(shí)際類型的 Class 對(duì)象的引用
Gum gum = new Gum();
Class gumClass = gum.getClass();
另外,你還可以調(diào)用 getSuperclass() 方法來(lái)得到父類的 class 對(duì)象,再用父類的 Class 對(duì)象調(diào)用該方法,重復(fù)多次,你就可以得到一個(gè)完整的類繼承結(jié)構(gòu)
Class 對(duì)象的 newInstance() 方法可以讓你在不知道一個(gè)的確切類型的時(shí)候創(chuàng)建這個(gè)類的對(duì)象,使用 newInstance() 來(lái)創(chuàng)建的類,必須帶有無(wú)參數(shù)的構(gòu)造器
Object obj = gumClass.newInstance();
當(dāng)然,由于得到的是 Object 的引用,目前你只能給它發(fā)送 Object 對(duì)象能接受的調(diào)用。如果你想請(qǐng)求具體對(duì)象才有的調(diào)用,你就得先獲取該對(duì)象的更多類型信息,并執(zhí)行轉(zhuǎn)型
Java 還提供了另一種生成類對(duì)象的引用:類字面常量,這樣做不僅更簡(jiǎn)單,而且更安全,因?yàn)樗诰幾g時(shí)就會(huì)收到檢查(不用放在 try 語(yǔ)句塊中),而且根除了對(duì) forName() 方法的調(diào)用,效率更高
Class gumClass = Gum.class;
類字面常量不僅可以用于普通類,也可以用于接口、數(shù)組以及基本數(shù)據(jù)類型。對(duì)于基本數(shù)據(jù)類型的包裝類,還有一個(gè)標(biāo)準(zhǔn)字段 Type,Type 字段是一個(gè)引用,指向?qū)?yīng)基本數(shù)據(jù)類型的 Class 對(duì)象,例如 int.class 就等價(jià)于 Integer.TYPE。還有一點(diǎn)值得注意的是:使用 .class 語(yǔ)法來(lái)獲得對(duì)類對(duì)象的引用不會(huì)觸發(fā)初始化
到這里我們都知道了,Class 引用總是指向某個(gè) Class 對(duì)象,而 Class 對(duì)象可以用于產(chǎn)生類的實(shí)例。不過(guò)自從 Java 引入泛型以后,我們就可以使用泛型對(duì) Class 引用所指向的 Class 對(duì)象的類型進(jìn)行限定,讓它的類型變得更具體些
Class intClass = int.class; Class<Integer> genericIntClass = int.class; intClass = genericIntClass; // 同一個(gè)東西 // genericIntClass = double.class 非法
好了,既然拿到了 Class 對(duì)象,那我們就可以這個(gè)類的類型信息,常用的方法如下:
方法 | 用途 |
---|---|
asSubclass(Class clazz) | 把傳遞的類的對(duì)象轉(zhuǎn)換成代表其子類的對(duì)象 |
Cast | 把對(duì)象轉(zhuǎn)換成代表類或是接口的對(duì)象 |
getClassLoader() | 獲得類的加載器 |
getClasses() | 返回一個(gè)數(shù)組,數(shù)組中包含該類中所有公共類和接口類的對(duì)象 |
getDeclaredClasses() | 返回一個(gè)數(shù)組,數(shù)組中包含該類中所有類和接口類的對(duì)象 |
forName(String className) | 根據(jù)類名返回類的對(duì)象 |
getName() | 獲得類的完整路徑名字 |
newInstance() | 創(chuàng)建類的實(shí)例 |
getPackage() | 獲得類的包 |
getSimpleName() | 獲得類的名字 |
getSuperclass() | 獲得當(dāng)前類繼承的父類的名字 |
getInterfaces() | 獲得當(dāng)前類實(shí)現(xiàn)的類或是接口 |
到目前為止,我們已知的 RTTI 類型包括:
RTTI 在 Java 中還有第三種形式,那就是關(guān)鍵字 instanceof,它返回一個(gè)布爾值,告訴我們對(duì)象是不是某個(gè)特定類型的實(shí)例,可以用提問(wèn)的方式使用它
Java 還提供了 Class.isInstance() 方法動(dòng)態(tài)檢測(cè)對(duì)象類型,例如
0 instance of String // 編譯報(bào)錯(cuò) String.class.isInstance(0) // 可以通過(guò)編譯
如果你不知道對(duì)象的確切類型,RTTI 會(huì)告訴你,但是有一個(gè)限制:必須在編譯時(shí)知道類型,才能使用 RTTI 檢測(cè)它。換句話說(shuō),編譯器必須知道你使用的所有類
看上去這并不是什么特別大的限制,但假設(shè)你引用了一個(gè)不在程序空間中的對(duì)象,比如你從磁盤文件或網(wǎng)絡(luò)連接中獲得大量的字節(jié),并被告知這些字節(jié)代表一個(gè)類,那該怎么辦呢?
類 Class 支持反射的概念,java.lang.reflect 庫(kù)中支持類 Field、Method、Constructor(每一個(gè)都實(shí)現(xiàn)了 Member 接口),這些類型的對(duì)象由 JVM 運(yùn)行時(shí)創(chuàng)建,以表示未知類中的對(duì)應(yīng)成員。通常我們不會(huì)直接使用反射,但反射可以用來(lái)支持其他 Java 特性,例如對(duì)象序列化等
Field 代表類的成員變量(成員變量也稱為類的屬性),Class 類中定義了如下方法用來(lái)獲取 Field 對(duì)象
方法 | 用途 |
---|---|
getField(String name) | 獲得某個(gè)公有的屬性對(duì)象 |
getFields() | 獲得所有公有的屬性對(duì)象 |
getDeclaredField(String name) | 獲得某個(gè)屬性對(duì)象 |
getDeclaredFields() | 獲得所有屬性對(duì)象 |
Field 類定義了如下方法設(shè)置成員變量的信息
方法 | 用途 |
---|---|
equals(Object obj) | 屬性與 obj 相等則返回 true |
get(Object obj) | 獲得 obj 中對(duì)應(yīng)的屬性值 |
set(Object obj, Object value) | 設(shè)置 obj 中對(duì)應(yīng)屬性值 |
Method 代表類的方法,Class 類中定義了如下方法用來(lái)獲取 Method 對(duì)象
方法 | 用途 |
---|---|
getMethod(String name, Class...<?> parameterTypes) | 獲得該類某個(gè)公有的方法 |
getMethods() | 獲得該類所有公有的方法 |
getDeclaredMethod(String name, Class...<?> parameterTypes) | 獲得該類某個(gè)方法 |
getDeclaredMethods() | 獲得該類所有方法 |
Method 類定義了如下方法對(duì)方法進(jìn)行調(diào)用
方法 | 用途 |
---|---|
invoke(Object obj, Object... args) | 傳遞 object 對(duì)象及參數(shù)調(diào)用該對(duì)象對(duì)應(yīng)的方法 |
Constructor 代表類的構(gòu)造器,Class 類中定義了如下方法用來(lái)獲取 Constructor 對(duì)象
方法 | 用途 |
---|---|
getConstructor(Class...<?> parameterTypes) | 獲得該類中與參數(shù)類型匹配的公有構(gòu)造方法 |
getConstructors() | 獲得該類的所有公有構(gòu)造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 獲得該類中與參數(shù)類型匹配的構(gòu)造方法 |
getDeclaredConstructors() | 獲得該類所有構(gòu)造方法 |
Constructor 代表類的構(gòu)造方法
方法 | 用途 |
---|---|
newInstance(Object... initargs) | 根據(jù)傳遞的參數(shù)創(chuàng)建類的對(duì)象 |
除了成員變量、方法和構(gòu)造器以外,反射還能獲取其他更多的信息,例如注解等,具體可查閱 Java API
反射的強(qiáng)大威力大家已經(jīng)看到了,通過(guò)反射我們甚至可以獲取到一些“本不應(yīng)該獲取”的信息,例如程序員為了降低耦合,往往會(huì)使用接口來(lái)隔離組件,但反射卻可以輕易破解
public interface A { void f(); } class B implements A { public void f() {} public void g() {} } public class InterfaceViolation { public static void main(String[] args) { A a = new B(); a.f(); // a.g(); // 編譯錯(cuò)誤 if (a instanceof B) { B b = (B) a; b.g(); } } }
通過(guò)使用 RTTI,我們發(fā)現(xiàn) a 是用 B 實(shí)現(xiàn)的,只要將其轉(zhuǎn)型為 B,我們就可以調(diào)用不在 A 中的方法。如果你不希望客戶端開(kāi)發(fā)者這樣做,那該如何解決呢?一種解決方案是直接聲明為實(shí)際類型,另一種則是讓實(shí)現(xiàn)類只具有包訪問(wèn)權(quán)限,這樣包外部的客戶端就看不到實(shí)現(xiàn)類了
除了這個(gè)以外,通過(guò)反射可以獲得所有成員信息,包括 private 的,通常這種違反訪問(wèn)權(quán)限的操作并不是十惡不赦的,也許還可以幫助你解決某些特定類型的問(wèn)題
代理是基本的設(shè)計(jì)模式之一,一個(gè)對(duì)象封裝真實(shí)對(duì)象,代替真實(shí)對(duì)象提供其他不同的操作,這些操作通常涉及到與真實(shí)對(duì)象的通信,因此代理通常充當(dāng)中間對(duì)象。下面是一個(gè)簡(jiǎn)單的靜態(tài)代理的示例:
interface Interface { void doSomething(); } class RealObject implements Interface { @Override public void doSomething() { System.out.println("doSomething"); } } class SimpleProxy implements Interface { private Interface proxied; SimpleProxy(Interface proxied) { this.proxied = proxied; } @Override public void doSomething() { System.out.println("SimpleProxy doSomething"); proxied.doSomething(); } } class SimpleProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); } }
當(dāng)你希望將額外的操作與真實(shí)對(duì)象做分離時(shí),代理可能會(huì)有所幫助,而 Java 的動(dòng)態(tài)代理更進(jìn)一步,不僅動(dòng)態(tài)創(chuàng)建代理對(duì)象,而且可以動(dòng)態(tài)地處理對(duì)代理方法的調(diào)用。在動(dòng)態(tài)代理上進(jìn)行的所有調(diào)用都會(huì)重定向到一個(gè)調(diào)用處理程序,該程序負(fù)責(zé)發(fā)現(xiàn)調(diào)用的內(nèi)容并決定如何處理,下面是一個(gè)簡(jiǎn)單示例:
class DynamicProxyHandler implements InvocationHandler { private Object proxied; DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(proxied, args); } } class SimpleDynamicProxy { public static void consumer(Interface iface) { iface.doSomething(); } public static void main(String[] args) { RealObject real = new RealObject(); Interface proxy = (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{Interface.class}, new DynamicProxyHandler(real)); consumer(proxy); } }
通過(guò)調(diào)用靜態(tài)方法 Proxy.newProxyInstance() 來(lái)創(chuàng)建動(dòng)態(tài)代理,該方法需要三個(gè)參數(shù):類加載器、希望代理實(shí)現(xiàn)的接口列表、以及接口 InvocationHandler 的一個(gè)實(shí)現(xiàn)。InvocationHandler 正是我們所說(shuō)的調(diào)用處理程序,動(dòng)態(tài)代理的所有調(diào)用會(huì)被重定向到調(diào)用處理程序,因此通常為調(diào)用處理程序的構(gòu)造函數(shù)提供一個(gè)真實(shí)對(duì)象的引用,以便執(zhí)行中間操作后可以轉(zhuǎn)發(fā)請(qǐng)求
關(guān)于反射機(jī)制在Java中的作用有哪些就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。