java:反射機制
Java反射機制介紹
- 文件概述
Java反射是Java被視為動態(或準動態)語言的一個關鍵性質,Java反射機制容許程式在執行時載入、探知、使用編譯期間完全未知的classes。換言之,Java可以載入一個執行時才得知名稱的class,獲得其完整結構。
在工作過程中,常會聽到反射這個概念,在平常的程式碼開發中也有看到和使用到,只是對它沒有一個較深入的瞭解,這次重新理解學習了一下反射機制,結合公司產品中的Hotweb框架,加深理解,本文為學習過程中的總結。
- 目標讀者
- 數通暢聯內部員工
- 廣大計算機愛好者
- 術語解釋
靜態載入類(編譯時載入類):大多數情況下都是使用這種形式。比如我們定義了一個類A,例項化採用A a = new A()接著就可以通過a物件呼叫相關方法或屬性,這就是靜態載入類的過程。
動態載入類(執行時載入類):所謂動態載入類,只需要通過Class c = Class.forName("類的全名")即可獲得類型別,然後通過呼叫A a = c.newInstance()方法即可例項化這個類。
本質的區別在於靜態載入的類的源程式在編譯時期載入(必須存在),而動態載入的類在編譯時期可以缺席(源程式不必存在)。
反射機制:在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。
- 反射功能
反射機制主要提供了以下功能:
- 在執行時判斷任意一個物件所屬的類;
- 在執行時構造任意一個類的物件;
- 在執行時判斷任意一個類所具有的成員變數和方法;
- 在執行時呼叫任意一個物件的方法;
- 生成動態代理。
在JDK中,主要通過以下類實現java反射機制,這些類都位於java.lang.reflect包中。
- Class:代表一個類
- Filed:代表類的成員變數或者說成員屬性
- Method:代表類的方法
- Constructor:代表類的構造方法
- Array:提供了動態建立陣列,以及訪問陣列元素的靜態方法
首先,需要定義測試使用的類
-
- 獲取類的Class物件
Class 類的例項,表示正在執行的 Java 應用程式中的類和介面。獲取類的Class物件有多種方式:
方式 |
方法 |
呼叫getClass |
Boolean var1 = true; Class<?> classType2 = var1.getClass(); System.out.println(classType2); 輸出:class java.lang.Boolean |
運用.class 語法 |
Class<?> classType4 = Boolean.class; System.out.println(classType4); 輸出:class java.lang.Boolean |
運用static method Class.forName() |
Class<?> classType5 = Class.forName("java.lang.Boolean"); System.out.println(classType5); 輸出:class java.lang.Boolean |
-
- 獲取類的Fields
可以通過反射機制得到某個類的某個屬性,然後改變對應於這個類的某個例項的該屬性值。JAVA 的Class<T>類提供了幾個方法獲取類的屬性。
方法 |
說明 |
public FieldgetField(String name) |
返回一個 Field 物件,它反映此 Class 物件所表示的類或介面的指定公共成員欄位 |
public Field[] getFields() |
返回一個包含某些 Field 物件的陣列,這些物件反映此 Class 物件所表示的類或介面的所有可訪問公共欄位 |
public FieldgetDeclaredField(Stringname) |
返回一個 Field 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告欄位 |
public Field[] getDeclaredFields() |
返回 Field 物件的一個數組,這些物件反映此 Class 物件所表示的類或介面所宣告的所有欄位 |
使用樣例如下圖所示:
可見getFields和getDeclaredFields區別:
getFields返回的是申明為public的屬性,包括父類中定義,
getDeclaredFields返回的是指定類定義的所有定義的屬性,不包括父類的。
-
- 獲取類的Method
通過反射機制得到某個類的某個方法,然後呼叫對應於這個類的某個例項的該方法
Class<T>類提供了幾個方法獲取類的方法。
方法 |
說明 |
public MethodgetMethod(String name,Class<?>... parameterTypes) |
返回一個 Method 物件,它反映此 Class 物件所表示的類或介面的指定公共成員方法 |
public Method[] getMethods() |
返回一個包含某些 Method 物件的陣列,這些物件反映此 Class 物件所表示的類或介面(包括那些由該類或介面宣告的以及從超類和超介面繼承的那些的類或介面)的公共 member 方法 |
public MethodgetDeclaredMethod(Stringname,Class<?>... parameterTypes) |
返回一個 Method 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告方法 |
public Method[] getDeclaredMethods() |
返回 Method 物件的一個數組,這些物件反映此 Class 物件表示的類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法 |
使用樣例如下圖所示:
-
- 獲取類的Constructor
通過反射機制得到某個類的構造器,然後呼叫該構造器建立該類的一個例項
Class<T>類提供了幾個方法獲取類的構造器。
方法 |
說明 |
public Constructor<T> getConstructor(Class<?>... parameterTypes) |
返回一個 Constructor 物件,它反映此 Class 物件所表示的類的指定公共構造方法 |
public Constructor<?>[] getConstructors() |
返回一個包含某些 Constructor 物件的陣列,這些物件反映此 Class 物件所表示的類的所有公共構造方法 |
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) |
返回一個 Constructor 物件,該物件反映此 Class 物件所表示的類或介面的指定構造方法 |
public Constructor<?>[] getDeclaredConstructors() |
返回 Constructor 物件的一個數組,這些物件反映此 Class 物件表示的類宣告的所有構造方法。它們是公共、保護、預設(包)訪問和私有構造方法 |
使用樣例如下圖所示:
-
- 新建類的例項
通過反射機制建立新類的例項,有幾種方法可以建立
方法 |
說明 |
呼叫類的Class物件的newInstance方法,該方法會呼叫物件的預設構造器,如果沒有預設構造器,會呼叫失敗 |
Class<?> classType = User.class; Object inst = classType.newInstance(); System.out.println(inst); 輸出: Person:Default Constructor User:Default Constructor |
呼叫預設Constructor物件的newInstance方法 |
Constructor<?> constructor1 = classType.getConstructor(); Object inst = constructor1.newInstance(); System.out.println(inst); 輸出: Person:Default Constructor User:Default Constructor |
呼叫帶引數Constructor物件的newInstance方法 |
Constructor<?> constructor2 = classType.getDeclaredConstructor(int.class, String.class); Object inst = constructor2.newInstance(20, "小明"); System.out.println(inst); 輸出: Person:Default Constructor User:Constructor with parameters |
-
- 呼叫類的函式
通過反射獲取類Method物件,呼叫Field的Invoke方法呼叫函式。
Class<?> classType = User.class; Object inst = classType.newInstance(); //物件 Method logMethod = classType.getDeclaredMethod("Log", String.class); //方法 logMethod.invoke(inst, "test");//呼叫User物件的Log方法(例項inst 的logMethod方法) |
呼叫後結果如下圖所示
上面失敗是由於沒有許可權呼叫private函式,這裡需要設定Accessible為true;
-
- 設定/獲取類的屬性值
通過反射獲取類的Field物件,呼叫Field方法設定或獲取值
Class<?> classType = User.class; Object inst = classType.newInstance(); Field intField = classType.getField("ageExtend");//User欄位 intField.setInt(inst, 30); //設定User物件的ageExtend屬性值 int value = intField.getInt(inst); //獲取User的ageExtend屬性(獲取inst物件的intField) System.out.println(value); |
呼叫後結果如下圖:
-
- 例項化物件並呼叫其方法
Person中有兩個方法,sayHi和sayHello
通過已定義的類字串變數,例項化該類,建立物件,然後根據傳入的方法引數,呼叫Person中的方法
String className = "com.aeai.reflect.test.Person"; Object obj = Class.forName(className).newInstance();//例項化Person類,建立物件 Method method1 = obj.getClass().getMethod("sayHi", String.class); Method method2 = obj.getClass().getMethod("sayHello", String.class); System.out.println(method1.invoke(obj, "小李")); System.out.println(method2.invoke(obj, "小王")); |
呼叫後結果如下圖:
不管是類名還是方法名,都可以定義在配置檔案中,然後讀取例項化、方法呼叫,通常Java web框架都是如此,數通暢聯Hotweb MVC框架也有類似機制,具體參加5.4節。
- 應用例項
- ArrayList中存放物件
在泛型為Integer的ArrayList中存放一個String型別的物件。
-
- 修改陣列的資訊
修改陣列的資訊例項,如下圖:
-
- 修改陣列的大小
修改陣列的大小例項,如下圖:
-
- Hotweb框架經典樣例
以數通暢聯基礎Hotweb MVC框架為例說明反射機制
首先,在DispatchServlet轉發請求至Handler時,需要例項化handler物件。
其中,在HandlerParser類中,使用handlerId通過配置檔案HandlerModule.xml讀取到對應Handler並例項化。
然後,通過instantiateHandler方法,新增handler的屬性,
最後,在例項化了handler物件後,通過傳入名為actionType的變數引數呼叫handler中對應名稱的方法。
在獲取方法名的時候,預設是prepareDisplay
最後,通過反射得到的Method物件,呼叫Field的Invoke方法呼叫指定的函式。