一、反射機制概述
Java 反射機制是在運行狀態中,對於任意一個類,都能夠獲得這個類的所有屬性和方法,對於任意一個對象都能夠調用它的任意一個屬性和方法。這種在運行時動態的獲取信息以及動態調用對象的方法的功能稱為Java 的反射機制。
Class 類與java.lang.reflect 類庫一起對反射的概念進行了支持,該類庫包含了Field,Method,Constructor類(每個類都實現了Member 接口)。這些類型的對象時由JVM 在運行時創建的,用以表示未知類里對應的成員。
這樣你就可以使用Constructor 創建新的對象,用get() 和set() 方法讀取和修改與Field 對象關聯的字段,用invoke() 方法調用與Method 對象關聯的方法。另外,還可以調用getFields() getMethods() 和 getConstructors() 等很便利的方法,以返回表示字段,方法,以及構造器的對象的數組。這樣匿名對象的信息就能在運行時被完全確定下來,而在編譯時不需要知道任何事情。
二、獲取字節碼的方式
在Java 中可以通過三種方法獲取類的字節碼(Class)對象
- 通過Object 類中的getClass() 方法,想要用這種方法必須要明確具體的類並且創建該類的對象。
- 所有數據類型都具備一個靜態的屬性.class 來獲取對應的Class 對象。但是還是要明確到類,然后才能調用類中的靜態成員。
- 只要通過給定類的字符串名稱就可以獲取該類的字節碼對象,這樣做擴展性更強。通過Class.forName() 方法完成,必須要指定類的全限定名,由於前兩種方法都是在知道該類的情況下獲取該類的字節碼對象,因此不會有異常,但是Class.forName() 方法如果寫錯類的路徑會報 ClassNotFoundException 的異常。
ackage com.jas.reflect; public class ReflectTest { public static void main(String[] args) { Fruit fruit = new Fruit(); Class<?> class1 = fruit.getClass(); //方法一 Class<?> class2 = Fruit.class; //方法二 Class class3 = null; try { //方法三,如果這里不指定類所在的包名會報 ClassNotFoundException 異常 class3 = Class.forName("com.jas.reflect.Fruit"); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(class1 + " " +class2 + " " + class3); } } class Fruit{}
三、通過反射機制獲取類信息
通過反射機制創建對象,在創建對象之前要獲得對象的構造函數對象,通過構造函數對象創建對應類的實例。
下面這段代碼分別在運行期間創建了一個無參與有參的對象實例。由於getConstructor() 方法與newInstance() 方法拋出了很多異常(你可以通過源代碼查看它們),這里就簡寫了直接拋出一個Exception,下同。
package com.jas.reflect; import java.lang.reflect.Constructor; public class ReflectTest { public static void main(String[] args) throws Exception { Class clazz = null; clazz = Class.forName("com.jas.reflect.Fruit"); Constructor<Fruit> constructor1 = clazz.getConstructor(); Constructor<Fruit> constructor2 = clazz.getConstructor(String.class); Fruit fruit1 = constructor1.newInstance(); Fruit fruit2 = constructor2.newInstance("Apple"); } } class Fruit{ public Fruit(){ System.out.println("無參構造器Run..........."); } public Fruit(String type){ System.out.println("有參構造器Run..........." + type); } }
輸出:
無參構造器Run………..
有參構造器Run………..Apple
通過反射機制獲取Class 中的屬性
package com.jas.reflect; import java.lang.reflect.Field; public class ReflectTest { public static void main(String[] args) throws Exception { Class<?> clazz = null; Field field = null; clazz = Class.forName("com.jas.reflect.Fruit"); //field = clazz.getField("num"); getField() 方法不能獲取私有的屬性 // field = clazz.getField("type"); 訪問私有字段時會報 NoSuchFieldException異常 field = clazz.getDeclaredField("type"); //獲取私有type 屬性 field.setAccessible(true); //對私有字段的訪問取消檢查 Fruit fruit = (Fruit) clazz.newInstance(); //創建無參對象實例 field.set(fruit,"Apple"); //為無參對象實例屬性賦值 Object type = field.get(fruit); //通過fruit 對象獲取屬性值 System.out.println(type); } } class Fruit{ public int num; private String type; public Fruit(){ System.out.println("無參構造器Run..........."); } public Fruit(String type){ System.out.println("有參構造器Run..........." + type); } }
輸出:
無參構造器Run………..
Apple
通過反射機制獲取Class 中的方法並運行。
package com.jas.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class ReflectTest { public static void main(String[] args) throws Exception { Class clazz = null; Method method = null; clazz = Class.forName("com.jas.reflect.Fruit"); Constructor<Fruit> fruitConstructor = clazz.getConstructor(String.class); Fruit fruit = fruitConstructor.newInstance("Apple"); //創建有參對象實例 method = clazz.getMethod("show",null); //獲取空參數show 方法 method.invoke(fruit,null); //執行無參方法 method = clazz.getMethod("show",int.class); //獲取有參show 方法 method.invoke(fruit,20); //執行有參方法 } } class Fruit{ private String type; public Fruit(String type){ this.type = type; } public void show(){ System.out.println("Fruit type = " + type); } public void show(int num){ System.out.println("Fruit type = " + type + ".....Fruit num = " + num); } }
輸出: Fruit type = Apple Fruit type = Apple…..Fruit num = 20
四、反射機制簡單應用(使用簡單工廠創建對象)
Class.forName() 生成的結果是在編譯時不可知的,因此所有的方法特征簽名信息都是在執行時被提取出來的。反射機制能過創建一個在編譯期完全未知的對象,並調用該對象的方法。
以下是反射機制與泛型的一個應用,通過一個工廠類創建不同類型的實例。
要創建對象的實例類Apple :
package com.jas.reflect; public interface Fruit {} class Apple implements Fruit{}
加載的配置文件config.properties:
Fruit=com.jas.reflect.Apple
工廠類BasicFactory :
package com.jas.reflect; import java.io.FileReader; import java.util.Properties; public class BasicFactory { private BasicFactory(){} private static BasicFactory bf = new BasicFactory(); private static Properties pro = null; static{ pro = new Properties(); try{ //通過類加載器加載配置文件 pro.load(new FileReader(BasicFactory.class.getClassLoader(). getResource("config.properties").getPath())); }catch (Exception e) { e.printStackTrace(); } } public static BasicFactory getFactory(){ return bf; } //使用泛型獲得通用的對象 public <T> T newInstance(Class<T> clazz){ String cName = clazz.getSimpleName(); //獲得字節碼對象的類名 String clmplName = pro.getProperty(cName); //根據字節碼對象的類名通過配置文件獲得類的全限定名 try{ return (T)Class.forName(clmplName).newInstance(); //根據類的全限定名創建實例對象 }catch (Exception e) { throw new RuntimeException(e); } } }
創建對象實例:
package com.jas.reflect; public class ReflectTest { public static void main(String[] args) throws Exception { Fruit fruit = BasicFactory.getFactory().newInstance(Fruit.class); System.out.println(fruit); } }
輸出
com.jas.reflect.Apple@4554617c
上面這個實例通過一個工廠創建不同對象的實例,通過這種方式可以降低代碼的耦合度,代碼得到了很大程度的擴展,以前要創建Apple 對象需要通過new 關鍵字創建Apple 對象,如果我們也要創建Orange 對象呢?是不是也要通過new 關鍵字創建實例並向上轉型為Fruit ,這樣做是麻煩的。
現在我們直接有一個工廠,你只要在配置文件中配置你要創建對象的信息,你就可以創建任何類型你想要的對象,是不是簡單很多了呢?可見反射機制的價值是很驚人的。
Spring 中的 IOC 的底層實現原理就是反射機制,Spring 的容器會幫我們創建實例,該容器中使用的方法就是反射,通過解析xml文件,獲取到id屬性和class屬性里面的內容,利用反射原理創建配置文件里類的實例對象,存入到Spring的bean容器中。
參考書籍:
《Java 編程思想》 Bruce Eckel 著 陳昊鵬 譯