反射概述
| 導航 反射 主題: |
反射是 Java 在執行時公開類功能的機制,允許 Java 程式列舉和訪問類的 方法、欄位和建構函式作為物件。換句話說,存在反映 Java 物件模型的基於物件的 *映象*,您可以使用這些物件在執行時使用 API 結構訪問物件的屬性,而不是編譯時語言結構。每個物件例項都有一個 getClass() 方法,繼承自 java.lang.Object,它返回一個物件,該物件包含該物件類的執行時表示;此物件是 java.lang.Class 的例項,進而具有返回該類的欄位、方法、建構函式、超類和其他屬性的方法。您可以使用這些反射物件訪問欄位、呼叫方法或例項化例項,所有這些都無需在編譯時依賴這些功能。Java 執行時提供用於反射的相應類。大多數支援反射的 Java 類都位於 java.lang.reflect 包 中。反射對於執行 Java 的動態操作非常有用 - 這些操作不是硬編碼到源程式中的,而是在執行時確定的。反射最重要的方面之一是 動態類載入。
示例:呼叫 main 方法
[edit | edit source]瞭解反射工作原理的一種方法是使用反射來模擬 Java 執行時環境 (JRE) 如何載入和執行類。當您呼叫 Java 程式時
| 控制檯
java 完全限定的類名 arg0 ... argn |
並向它傳遞命令列引數,JRE 必須
- 將命令列引數 arg0 ... argn 放入
String[] 陣列中 - 動態載入由 完全限定的類名 指定的目標類
- 訪問
publicstaticvoidmain(String[])方法 - 呼叫
main方法,將字串陣列 mainString[]傳遞給它。
步驟 2、3 和 4 可以使用 Java 反射完成。以下是如何載入 Distance 類、定位 main 方法(參見 理解 Java 程式)並透過反射呼叫它的示例。
程式碼部分 10.1:main() 方法呼叫。
public static void invokeMain()
throws ClassNotFoundException,
ExceptionInInitializerError,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException,
NoSuchMethodException,
SecurityException {
Class<?> distanceClass = Class.forName("Distance");
String[] points = {"0", "0", "3", "4"};
Method mainMethod = distanceClass.getMethod("main", String[].class);
Object result = mainMethod.invoke(null, (Object) points);
}
|
這段程式碼顯然比簡單地呼叫更復雜
程式碼部分 10.2:main() 方法呼叫。
Distance.main(new String[]{"0", "0", "3", "4"});
|
但是,主 Java 執行時不知道 Distance 類。要執行的類的名稱是執行時值。反射允許 Java 程式使用類,即使在編寫程式時不知道這些類。讓我們探討一下 invokeMain 方法在做什麼。第 9 行的第一條語句是 動態類載入 的示例。forName() 方法將載入 Java 類並返回 java.lang.Class 的例項,該例項是載入類後產生的。在本例中,我們從預設包中載入類 "Distance"。我們將類物件儲存在名為 distanceClass 的區域性變數中;它的型別是 Class<?>。第 10 行的第二條語句只是建立一個帶有四個命令列引數的 String 陣列,我們希望將其傳遞給 Distance 類的 main 方法。第 11 行的第三條語句對 Distance 類執行反射操作。getMethod() 方法是為 Class 類定義的。它接受可變數量的引數:方法名稱是第一個引數,其餘引數是每個 main 引數的型別。方法名稱很簡單:我們希望呼叫 main 方法,因此我們將名稱 "main" 傳遞給它。然後,我們為每個方法引數新增一個 Class 變數。main 接受一個引數 (String[] args),因此我們新增一個表示 String[] 的單個 Class 元素。getMethod 方法的返回型別是 java.lang.reflect.Method;我們將結果儲存在一個名為 mainMethod 的區域性變數中。最後,我們透過呼叫 Method 例項的 invoke() 方法來呼叫該方法。此方法的第一個引數是要呼叫它的例項,其餘引數是呼叫者的引數。由於我們正在呼叫靜態方法而不是例項方法,因此我們將 null 作為例項引數傳遞。由於我們只有一個引數,因此將其作為第二個引數傳遞。但是,我們必須將引數轉換為 Object 以指示陣列是引數,而不是引數在陣列中。有關此內容的更多詳細資訊,請參見可變引數。
程式碼部分 10.3:invoke() 呼叫。
Object result = mainMethod.invoke(null, arguments);
|
invoke() 方法返回一個 Object,它將包含反射方法返回的結果。在本例中,我們的 main 方法是一個 void 方法,因此我們忽略了返回型別。此簡短 invokeMain 方法中的大多數方法可能會引發各種異常。該方法在其簽名中聲明瞭所有異常。以下是可能會引發異常的簡要概述
Class.forName(String)將引發ClassNotFoundException,如果無法找到指定類。Class.forName(String)將引發ExceptionInInitializerError,如果由於靜態初始化程式引發異常或靜態欄位的初始化引發異常而無法載入該類。Class.getMethod(String name, Class parameterTypes[])將引發NoSuchMethodException,如果找不到匹配的方法,或者該方法不是公共的(使用getDeclaredMethod獲取非公共方法)。SecurityException,如果安裝了安全管理器並且呼叫該方法會導致訪問衝突(例如,該方法位於專為內部使用而設計的sun.*包中)。
Method.invoke(Object instance, Object... arguments)可能會引發IllegalAccessException,如果以違反其訪問修飾符的方式呼叫此方法。IllegalArgumentException出於多種原因,包括- 傳遞未實現此方法的例項。
- 實際引數與方法引數不匹配
InvocationTargetException,如果底層方法(本例中的main)引發異常。
除了這些異常之外,這些方法還可能引發錯誤和執行時異常。
