Java讓咱們在運行時識別對象和類的信息,主要有兩種形式:java
一種是傳統的RTTI(Run-Time Type Identification),它假定咱們在編譯時已經知道了全部的類型信息;
另外一種是反射機制,它容許咱們在運行時發現和使用類的信息。使用的前提條件:必須先獲得表明的字節碼的Class,Class類用於表示.class文件(字節碼)。api
1、反射的介紹
Java反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲Java語言的反射機制。數組
要想解剖一個類,必須先要獲取到該類的字節碼文件對象。而解剖使用的就是Class類中的方法。因此先要獲取到每個字節碼文件對應的Class類型的對象.。安全
2、反射的「反」
反射之中包含了一個「反」字,因此想要解釋反射就必須先從「正」開始解釋。框架
通常狀況下,咱們使用某個類時一定知道它是什麼類,是用來作什麼的。因而咱們直接對這個類進行實例化,以後使用這個類對象進行操做。性能
如:this
Phone phone = new Phone(); //直接初始化,「正射」 phone.setPrice(4);
上面這樣子進行類對象的初始化,咱們能夠理解爲「正」。spa
而反射則是一開始並不知道我要初始化的類對象是什麼,天然也沒法使用 new 關鍵字來建立對象了。hibernate
這時候,咱們使用 JDK 提供的反射 API 進行反射調用:代理
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);
上面兩段代碼的執行結果,實際上是徹底同樣的。可是其思路徹底不同,第一段代碼在未運行時就已經肯定了要運行的類(Phone),而第二段代碼則是在運行時經過字符串值才得知要運行的類(com.xxp.reflect.Phone)。
因此說什麼是反射?反射就是在運行時才知道要操做的類是什麼,而且能夠在運行時獲取類的完整構造,並調用對應的方法。
上面提到的示例程序,其完整的程序代碼以下:
public class Phone { private int price; public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public static void main(String[] args) throws Exception{ //正常的調用 Phone phone = new Phone(); phone.setPrice(5000); System.out.println("Phone Price:" + phone.getPrice()); //使用反射調用 Class clz = Class.forName("com.xxp.api.Phone"); Method setPriceMethod = clz.getMethod("setPrice", int.class); Constructor phoneConstructor = clz.getConstructor(); Object phoneObj = phoneConstructor.newInstance(); setPriceMethod.invoke(phoneObj, 6000); Method getPriceMethod = clz.getMethod("getPrice"); System.out.println("Phone Price:" + getPriceMethod.invoke(phoneObj)); } }
從這個簡單的例子能夠看出,通常狀況下咱們使用反射獲取一個對象的步驟:
//獲取類的 Class 對象實例 Class clz = Class.forName("com.xxp.api.Phone"); //根據 Class 對象實例獲取 Constructor 對象 Constructor phoneConstructor = clz.getConstructor(); //使用 Constructor 對象的 newInstance 方法獲取反射類對象 Object phoneObj = phoneConstructor.newInstance();
而若是要調用某一個方法,則須要通過下面的步驟:
//獲取方法的 Method 對象 Method setPriceMethod = clz.getMethod("setPrice", int.class); //利用 invoke 方法調用方法 setPriceMethod.invoke(phoneObj, 6000);
到這裏,咱們已經可以掌握反射的基本使用。但若是要進一步掌握反射,還須要對反射的經常使用 API 有更深刻的理解。
3、反射的API
與Java反射相關的類以下:
① Class: 表明類的實體,在運行的Java應用程序中表示類和接口。
(1) 得到類相關方法
方法 | 用途 |
asSubclass(Class<U> clazz) | 把傳遞的類的對象轉換成表明其子類的對象 |
Cast | 把對象轉換成表明類或是接口的對象 |
getClassLoader() | 得到類的加載器 |
getClasses() | 返回一個數組,數組中包含該類中全部公共類和接口類的對象 |
getDeclaredClasses() | 返回一個數組,數組中包含該類中全部類和接口類的對象 |
forName(String className) | 根據類名返回類的對象 |
getName() | 得到類的完整路徑名字 |
newInstance() | 建立類的實例 |
getPackage() | 得到類的包 |
getSimpleName() | 得到類的名字 |
getSuperclass() | 得到當前類繼承的父類的名字 |
getInterfaces() | 得到當前類實現的類或是接口 |
(2) 得到類中屬性相關的方法
方法 | 用途 |
getField(String name) | 得到某個公有的屬性對象 |
getFields() | 得到全部公有的屬性對象 |
getDeclaredField(String name) | 得到某個屬性對象 |
getDeclaredFields() | 得到全部屬性對象 |
(3) 得到類中註解相關的方法
方法 | 用途 |
getConstructor(Class...<?> parameterTypes) | 得到該類中與參數類型匹配的公有構造方法 |
getConstructors() | 得到該類的全部公有構造方法 |
getDeclaredConstructor(Class...<?> parameterTypes) | 得到該類中與參數類型匹配的構造方法 |
getDeclaredConstructors() | 得到該類全部構造方法 |
(4) 類中其餘重要的方法
方法 | 用途 |
isAnnotation() | 若是是註解類型則返回true |
isAnnotationPresent(Class<? extends Annotation> annotationClass) | 若是是指定類型註解類型則返回true |
isAnonymousClass() | 若是是匿名類則返回true |
isArray() | 若是是一個數組類則返回true |
isEnum() | 若是是枚舉類則返回true |
isInstance(Object obj) | 若是obj是該類的實例則返回true |
isInterface() | 若是是接口類則返回true |
isLocalClass() | 若是是局部類則返回true |
isMemberClass() | 若是是內部類則返回true |
getAnnotation(Class<A> annotationClass) | 獲取修飾類的指定註解 |
② Field:表類的成員變量(成員變量也稱爲類的屬性)
方法 | 用途 |
equals(Object obj) | 屬性與obj相等則返回true |
get(Object obj) | 得到obj中對應的屬性值 |
set(Object obj, Object value) | 設置obj中對應屬性值 |
③ Method:表明類的方法
方法 | 用途 |
invoke(Object obj, Object... args) | 傳遞object對象及參數調用該對象對應的方法 |
④ Constructor:表明類的構造方法
方法 | 用途 |
newInstance(Object... initargs) | 根據傳遞的參數建立類的對象 |
1. 得到反射中的Class對象
在反射中,要獲取一個類或調用一個類的方法,咱們首先須要獲取到該類的 Class 對象。
在 Java API 中,獲取 Class 類對象有三種方法:
(1) 使用 Class.forName 靜態方法。當知道某類的全路徑名時,可使用此方法獲取 Class 類對象。用的最多,但可能拋出 ClassNotFoundException 異常。
Class c1 = Class.forName(「java.lang.String」);
(2) 直接經過 類名.class 的方式獲得,該方法最爲安全可靠,程序性能更高。這說明任何一個類都有一個隱含的靜態成員變量 class。這種方法只適合在編譯前就知道操做的 Class。
Class c2 = String.class;
(3) 經過對象調用 getClass() 方法來獲取,一般應用在:好比你傳過來一個 Object類型的對象,而我不知道你具體是什麼類,用這種方法。
String str = new String("Hello"); Class c3 = str.getClass();
須要注意的是:一個類在 JVM 中只會有一個 Class 實例,即咱們對上面獲取的 c一、c2和c3進行 equals 比較,發現都是true。
2. 經過反射建立類對象
經過反射建立類對象主要有兩種方式:經過 Class 對象的 newInstance() 方法、經過 Constructor 對象的 newInstance() 方法。
(1) 經過 Class 對象的 newInstance() 方法。
Class clz = Phone.class; Phone phone = (Phone)clz.newInstance();
(2) 經過 Constructor 對象的 newInstance() 方法
Class clz = Phone.class; Constructor constructor = clz.getConstructor(); Phone phone= (Phone)constructor.newInstance();
經過 Constructor 對象建立類對象能夠選擇特定構造方法,而經過 Class 對象則只能使用默認的無參數構造方法。下面的代碼就調用了一個有參數的構造方法進行了類對象的初始化。
Class clz = Phone.class; Constructor constructor = clz.getConstructor(String.class, int.class); Phone phone = (Phone)constructor.newInstance("華爲",6666);
3. 經過反射獲取類屬性、方法、構造器
咱們經過 Class 對象的 getFields() 方法能夠獲取 Class 類的屬性,但沒法獲取私有屬性。
Class clz = Phone.class; Field[] fields = clz.getFields(); for (Field field : fields) { System.out.println(field.getName()); }
而若是使用 Class 對象的 getDeclaredFields() 方法則能夠獲取包括私有屬性在內的全部屬性:
Class clz = Phone.class; Field[] fields = clz.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); }
與獲取類屬性同樣,當咱們去獲取類方法、類構造器時,若是要獲取私有方法或私有構造器,則必須使用有 declared 關鍵字的方法。
4、反射源碼討論
當咱們懂得了如何使用反射後,今天咱們就來看看 JDK 源碼中是如何實現反射的。或許你們平時沒有使用過反射,可是在開發 Web 項目的時候會遇到過下面的異常:
java.lang.NullPointerException ... sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:369)
能夠看到異常堆棧指出了異常在 Method 的第 369 的 invoke 方法中,其實這裏指的 invoke 方法就是咱們反射調用方法中的 invoke。
Method method = clz.getMethod("setPrice", int.class); method.invoke(object, 6); //就是這裏的invoke方法
例如咱們常用的 Spring 配置中,常常會有相關 Bean 的配置
<bean class="com.xxp.Phone">
</bean>
5、反射總結
6、new對象和反射獲得對象的區別
- 在使用反射的時候,必須確保這個類已經加載並已經鏈接了。使用new的時候,這個類能夠沒有被加載,也能夠已經被加載。
- new關鍵字能夠調用任何public構造方法,而反射只能調用無參構造方法。
- new關鍵字是強類型的,效率相對較高。 反射是弱類型的,效率低。
- 反射提供了一種更加靈活的方式建立對象,獲得對象的信息。如Spring 中AOP等的使用,動態代理的使用,都是基於反射的。解耦。