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