Java反射(框架設計的靈魂)

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>
當咱們在 XML 文件中配置了上面這段配置以後,Spring 便會在啓動的時候利用反射去加載對應的 Phone類。而當 Apple 類不存在或發生啓發異常時,異常堆棧便會將異常指向調用的 invoke 方法。
從這裏能夠看出,咱們日常不少框架都使用了反射,而反射中最重要的就是 Method 類的 invoke 方法了。

5、反射總結

咱們知道反射機制容許程序在運行時取得任何一個已知名稱的class的內部信息,包括包括其modifiers(修飾符),fields(屬性),methods(方法)等,並可於運行時改變fields內容或調用methods。那麼咱們即可以更靈活的編寫代碼,代碼能夠在運行時裝配,無需在組件之間進行源代碼連接,下降代碼的耦合度;還有動態代理的實現;JDBC原生代碼註冊驅動;hibernate 的實體類;Spring的AOP等等。可是凡事都有兩面性,反射使用不當會形成很高的資源消耗!

6、new對象和反射獲得對象的區別

  • 在使用反射的時候,必須確保這個類已經加載並已經鏈接了。使用new的時候,這個類能夠沒有被加載,也能夠已經被加載。
  • new關鍵字能夠調用任何public構造方法,而反射只能調用無參構造方法。
  • new關鍵字是強類型的,效率相對較高。 反射是弱類型的,效率低。
  • 反射提供了一種更加靈活的方式建立對象,獲得對象的信息。如Spring 中AOP等的使用,動態代理的使用,都是基於反射的。解耦。
相關文章
相關標籤/搜索