要想理解反射的原理,首先要了解什么是類型信息。JAVA讓我們?cè)谶\(yùn)行時(shí)識(shí)別對(duì)象和類的信息,主要有兩種方式:一種是傳統(tǒng)的RTTI(Run-Time Type Identification),它假定我們?cè)诰幾g時(shí)已經(jīng)知道了所有的類型信息;另一種是反射機(jī)制,它允許我們?cè)谶\(yùn)行時(shí)發(fā)現(xiàn)和使用類的信息。
使用的前提條件:必須先得到代表的字節(jié)碼的Class,Class類用于表示.class文件(字節(jié)碼)
一、反射的概述
JAVA反射機(jī)制是在運(yùn)行狀態(tài)中,對(duì)于任意一個(gè)類,都能夠知道這個(gè)類的所有屬性和方法;對(duì)于任意一個(gè)對(duì)象,都能夠調(diào)用它的任意一個(gè)方法和屬性;這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為Java語(yǔ)言的反射機(jī)制。
要想解剖一個(gè)類,必須先要獲取到該類的字節(jié)碼文件對(duì)象。而解剖使用的就是Class類中的方法。所以先要獲取到每一個(gè)字節(jié)碼文件對(duì)應(yīng)的Class類型的對(duì)象.。
反射就是把Java類中的各種成分映射成一個(gè)個(gè)的Java對(duì)象。
例如:
一個(gè)類有:成員變量、方法、構(gòu)造方法、包等等信息,利用反射技術(shù)可以對(duì)一個(gè)類進(jìn)行解剖,把各個(gè)組成部分映射成一個(gè)個(gè)對(duì)象。
(其實(shí):一個(gè)類中這些成員方法、構(gòu)造方法,在加入類中都有一個(gè)類來(lái)描述)
如圖是類的正常加載過(guò)程:反射的原理在于class對(duì)象。
熟悉一下加載的時(shí)候:Class對(duì)象的由來(lái)是將class文件讀入內(nèi)存,并為之創(chuàng)建一個(gè)Class對(duì)象。
類的正常加載過(guò)程
二、反射的理解
反射之中包含了一個(gè)「反」字,所以想要解釋反射就必須先從「正」開(kāi)始解釋。
一般情況下,我們使用某個(gè)類時(shí)必定知道它是什么類,是用來(lái)做什么的。于是我們直接對(duì)這個(gè)類進(jìn)行實(shí)例化,之后使用這個(gè)類對(duì)象進(jìn)行操作。
如:
Phone phone = new Phone(); //直接初始化,「正射」
phone.setPrice(4);
上面這樣子進(jìn)行類對(duì)象的初始化,我們可以理解為「正」。
而反射則是一開(kāi)始并不知道我要初始化的類對(duì)象是什么,自然也無(wú)法使用 new 關(guān)鍵字來(lái)創(chuàng)建對(duì)象了。
這時(shí)候,我們使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用:
Class clz = Class.forName("com.xxp.reflect.Phone");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
上面兩段代碼的執(zhí)行結(jié)果,其實(shí)是完全一樣的。但是其思路完全不一樣,第一段代碼在未運(yùn)行時(shí)就已經(jīng)確定了要運(yùn)行的類(Phone),而第二段代碼則是在運(yùn)行時(shí)通過(guò)字符串值才得知要運(yùn)行的類(com.xxp.reflect.Phone)。
所以說(shuō)什么是反射?反射就是在運(yùn)行時(shí)才知道要操作的類是什么,并且可以在運(yùn)行時(shí)獲取類的完整構(gòu)造,并調(diào)用對(duì)應(yīng)的方法。
一般情況下我們使用反射獲取一個(gè)對(duì)象的步驟:
//獲取類的 Class 對(duì)象實(shí)例
Class clz = Class.forName("com.xxp.api.Phone");
//根據(jù) Class 對(duì)象實(shí)例獲取 Constructor 對(duì)象
Constructor phoneConstructor = clz.getConstructor();
//使用 Constructor 對(duì)象的 newInstance 方法獲取反射類對(duì)象
Object phoneObj = phoneConstructor.newInstance();
而如果要調(diào)用某一個(gè)方法,則需要經(jīng)過(guò)下面的步驟:
//獲取方法的 Method 對(duì)象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
//利用 invoke 方法調(diào)用方法
setPriceMethod.invoke(phoneObj, 6000);
到這里,我們已經(jīng)能夠掌握反射的基本使用。但如果要進(jìn)一步掌握反射,還需要對(duì)反射的常用 API 有更深入的理解。
三、反射的常用API
在 JDK 中,反射相關(guān)的 API 可以分為下面幾個(gè)方面:獲取反射的 Class 對(duì)象、通過(guò)反射創(chuàng)建類對(duì)象、通過(guò)反射獲取類屬性方法及構(gòu)造器。
反射常用API:
獲取反射中的Class對(duì)象
在反射中,要獲取一個(gè)類或調(diào)用一個(gè)類的方法,我們首先需要獲取到該類的 Class 對(duì)象。
在 Java API 中,獲取 Class 類對(duì)象有三種方法:
- 使用 Class.forName 靜態(tài)方法。當(dāng)知道某類的全路徑名時(shí),可以使用此方法獲取 Class 類對(duì)象。用的最多,但可能拋出 ClassNotFoundException 異常。
- Class c1 = Class.forName(“java.lang.String”);
- 直接通過(guò) 類名.class 的方式得到,該方法最為安全可靠,程序性能更高。這說(shuō)明任何一個(gè)類都有一個(gè)隱含的靜態(tài)成員變量 class。這種方法只適合在編譯前就知道操作的 Class。
- Class c2 = String.class;
- 通過(guò)對(duì)象調(diào)用 getClass() 方法來(lái)獲取,通常應(yīng)用在:比如你傳過(guò)來(lái)一個(gè) Object類型的對(duì)象,而我不知道你具體是什么類,用這種方法。
String str = new String("Hello");
Class c3 = str.getClass();
需要注意的是:一個(gè)類在 JVM 中只會(huì)有一個(gè) Class 實(shí)例,即我們對(duì)上面獲取的 c1、c2和c3進(jìn)行 equals 比較,發(fā)現(xiàn)都是true。
通過(guò)反射創(chuàng)建類對(duì)象
通過(guò)反射創(chuàng)建類對(duì)象主要有兩種方式:通過(guò) Class 對(duì)象的 newInstance() 方法、通過(guò) Constructor 對(duì)象的 newInstance() 方法。
- 通過(guò) Class 對(duì)象的 newInstance() 方法。
- Class clz = Phone.class;
- Phone phone = (Phone)clz.newInstance();
- 通過(guò) Constructor 對(duì)象的 newInstance() 方法
Class clz = Phone.class;
Constructor constructor = clz.getConstructor();
Phone phone= (Phone)constructor.newInstance();
通過(guò) Constructor 對(duì)象創(chuàng)建類對(duì)象可以選擇特定構(gòu)造方法,而通過(guò) Class 對(duì)象則只能使用默認(rèn)的無(wú)參數(shù)構(gòu)造方法。下面的代碼就調(diào)用了一個(gè)有參數(shù)的構(gòu)造方法進(jìn)行了類對(duì)象的初始化。
Class clz = Phone.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Phone phone = (Phone)constructor.newInstance("華為",6666);
通過(guò)反射獲取類屬性、方法、構(gòu)造器
我們通過(guò) Class 對(duì)象的 getFields() 方法可以獲取 Class 類的屬性,但無(wú)法獲取私有屬性。
Class clz = Phone.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
輸出結(jié)果是:
price
而如果使用 Class 對(duì)象的 getDeclaredFields() 方法則可以獲取包括私有屬性在內(nèi)的所有屬性:
Class clz = Phone.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
輸出結(jié)果是:
name
price
與獲取類屬性一樣,當(dāng)我們?nèi)カ@取類方法、類構(gòu)造器時(shí),如果要獲取私有方法或私有構(gòu)造器,則必須使用有 declared 關(guān)鍵字的方法。
附:查閱 API 可以看到 Class 有很多方法:
getName():獲得類的完整名字。
getFields():獲得類的public類型的屬性。
getDeclaredFields():獲得類的所有屬性。包括private 聲明的和繼承類
getMethods():獲得類的public類型的方法。
getDeclaredMethods():獲得類的所有方法。包括private 聲明的和繼承類
getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數(shù)指定方法的名字,parameterTypes 參數(shù)指定方法的參數(shù)類型。
getConstructors():獲得類的public類型的構(gòu)造方法。
getConstructor(Class[] parameterTypes):獲得類的特定構(gòu)造方法,parameterTypes 參數(shù)指定構(gòu)造方法的參數(shù)類型。
newInstance():通過(guò)類的不帶參數(shù)的構(gòu)造方法創(chuàng)建這個(gè)類的一個(gè)對(duì)象。
四、反射源碼解析
當(dāng)我們懂得了如何使用反射后,今天我們就來(lái)看看 JDK 源碼中是如何實(shí)現(xiàn)反射的。或許大家平時(shí)沒(méi)有使用過(guò)反射,但是在開(kāi)發(fā) Web 項(xiàng)目的時(shí)候會(huì)遇到過(guò)下面的異常:
java.lang.NullPointerException ... sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:369)
可以看到異常堆棧指出了異常在 Method 的第 369 的 invoke 方法中,其實(shí)這里指的 invoke 方法就是我們反射調(diào)用方法中的 invoke。
Method method = clz.getMethod("setPrice", int.class);
method.invoke(object, 6); //就是這里的invoke方法
例如我們經(jīng)常使用的 Spring 配置中,經(jīng)常會(huì)有相關(guān) Bean 的配置:
<bean class="com.xxp.Phone"> </bean>
當(dāng)我們?cè)?XML 文件中配置了上面這段配置之后,Spring 便會(huì)在啟動(dòng)的時(shí)候利用反射去加載對(duì)應(yīng)的 Phone類。而當(dāng) Apple 類不存在或發(fā)生啟發(fā)異常時(shí),異常堆棧便會(huì)將異常指向調(diào)用的 invoke 方法。
從這里可以看出,我們平常很多框架都使用了反射,而反射中最重要的就是 Method 類的 invoke 方法了。
五、反射總結(jié)
我們知道反射機(jī)制允許程序在運(yùn)行時(shí)取得任何一個(gè)已知名稱的class的內(nèi)部信息,包括包括其modifiers(修飾符),fields(屬性),methods(方法)等,并可于運(yùn)行時(shí)改變fields內(nèi)容或調(diào)用methods。那么我們便可以更靈活的編寫(xiě)代碼,代碼可以在運(yùn)行時(shí)裝配,無(wú)需在組件之間進(jìn)行源代碼鏈接,降低代碼的耦合度;還有動(dòng)態(tài)代理的實(shí)現(xiàn);JDBC原生代碼注冊(cè)驅(qū)動(dòng);hibernate 的實(shí)體類;Spring的AOP等等。但是凡事都有兩面性,反射使用不當(dāng)會(huì)造成很高的資源消耗!
六、new對(duì)象和反射得到對(duì)象的區(qū)別
- 在使用反射的時(shí)候,必須確保這個(gè)類已經(jīng)加載并已經(jīng)連接了。使用new的時(shí)候,這個(gè)類可以沒(méi)有被加載,也可以已經(jīng)被加載。
- new關(guān)鍵字可以調(diào)用任何public構(gòu)造方法,而反射只能調(diào)用無(wú)參構(gòu)造方法。
- new關(guān)鍵字是強(qiáng)類型的,效率相對(duì)較高。 反射是弱類型的,效率低。
- 反射提供了一種更加靈活的方式創(chuàng)建對(duì)象,得到對(duì)象的信息。如Spring 中AOP等的使用,動(dòng)態(tài)代理的使用,都是基于反射的。解耦。






