介紹反射機制
Java 的反射機制允許在程式運行期間,借助反射 API 獲取類的內部資訊,并能直接操作物件的內部屬性及方法,
Java 反射機制提供的功能:
- 在運行時,使用反射分析類的能力,獲取有關類的一切資訊(類所在的包、類實作的介面、標注的注解、類的資料域、類的構造器、類的方法等)
- 在運行時,使用反射分析物件,設定實體域的值,查看實體域的值,
- 反射機制允許你呼叫任意方法(類的構造器方法、類的成員方法 等)
反射是一種功能強大且復雜的機制,使用反射機制的主要人員是工具構造者,而不是應用程式員,
Class 類
在程式運行期間,Java 運行時系統始終為所有的物件維護一個被稱為運行時的型別標識,這個資訊跟蹤著每個物件所屬的類,虛擬機利用運行時型別資訊選擇相應的方法執行,
然而,可以通過專門的 Java 類訪問這些資訊,保存這些資訊的類被稱為 Class,Object 類中的 getClass() 方法將會回傳一個 Class 型別的實體,
如同用一個 Employee 物件表示一個特定的雇員屬性一樣,一個 Class 物件將表示一個特定類的屬性,
虛擬機為每個型別管理一個 Class 物件,因此,可以利用 == 運算子實作兩個 Class 物件比較的操作,
// 獲得 Class 物件的多種方式:
public static void main(String[] args) {
// 方式 1
// 如果 T 是任意的 Java 型別 (或 void 關鍵字), T.class 將代表匹配的 Class 物件,
Class<Person> clazz1 = Person.class;
// 方式 2
Person person = new Person();
Class clazz2 = person.getClass();
// 方式 3
try {
Class clazz3 = Class.forName("類的路徑");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
// 方式4
// 獲取到 ClassLoader(這里獲取到的是:AppClassLoader)
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
try {
Class clazz4 = classLoader.loadClass("類的路徑");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
還有一個很有用的方法:Class 類的 newlnstance(),可以用這個方法來動態地創建一個類的實體,newlnstance() 方法呼叫默認的構造器(沒有引數的構造器)初始化新創建的物件,如果這個類沒有默認的構造器,就會拋出一個 InstantiationException 例外,
將 Class 類的 forName() 方法與 Class 類的 newlnstance() 方法配合起來使用,可以根據存盤在字串中的類名創建一個物件,
public static void main(String[] args) throws Exception {
String className = "java.util.Random";
Object object = Class.forName(className).newInstance();
}
如果需要以這種方式向希望按名稱創建的類的構造器提供引數,就不要使用上面那條陳述句,而必須使用 Constructor 類中的 newlnstance() 方法,
分析類的能力
在運行時,使用反射分析類的能力,
下面簡要地介紹一下反射機制最重要的內容:檢查類的結構,在 java.lang.reflect 包中有三個類 Field、Method 和 Constructor 分別用于描述類的資料域、類的方法和類的構造器,
這三個類都有一個叫做 getName() 的方法,用來回傳專案的名稱,
Field 類有一個 getType() 方法,用來回傳描述資料域所屬型別的 Class 物件,
Method 類和 Constructor 類有能夠報告引數型別的方法,Method 類還有一個可以報告回傳型別的方法,
這三個類還有一個叫做 getModifiers() 的方法,它將回傳一個整型數值,用不同的位開關描述 public 和 static 這樣的修飾符使用狀況,另外, 還可以利用 java.lang.reflect 包中的 Modifier 類的靜態方法分析 getModifiers() 回傳的整型數值,例如,可以使用 Modifier 類中的 isPublic()、isPrivate() 或 isFinal() 判斷方法或構造器是否是 public、private 或 final 的,我們需要做的全部作業就是呼叫 Modifier 類的相應方法,并對回傳的整型數值進行分析,另外,還可以利用 Modifier.toString() 方法將修飾符列印出來,
Class 類的 getFields()、getMethods() 和 getConstructors() 方法將分別回傳類中宣告的 public 域、public 方法和 public 構造器陣列,其中包括父類的公有成員,
Class 類的 getDeclareFields()、getDeclareMethods() 和 getDeclaredConstructors() 方法將分別回傳類中宣告的全部的資料域、全部的方法和全部的構造器,其中包括私有和受保護成員,但不包括父類的成員,
分析物件
在運行時,使用反射分析物件,
從前面一節中,已經知道如何查看任意物件的資料域的名稱和型別:
- 獲得對應的 Class 物件,
- 呼叫 Class 物件的 getDeclaredFields() 方法,
本節將進一步查看資料域的實際內容,當然,在撰寫程式時,如果知道想要査看的資料域的名稱和型別,查看指定的資料域是一件很容易的事情,而利用反射機制可以查看在編譯時還不清楚的資料域,
查看資料域值的關鍵方法是 Field 類中的 get() 方法,如果 f 是一個 Field 型別的物件(例如,通過 getDeclaredFields() 得到的物件),obj 是某個包含 f 域的類的物件,f.get(obj) 將回傳一個物件,其值為 obj 物件的 f 域的當前值,
當然,可以獲得就可以設定,呼叫 f.set(obj, value) 可以將 obj 物件的 f 域設定成新值,
public static void main(String[] args) {
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989);
Class cl = harry.getClass();
// the class object representing Employee
Field f = cl.getDeclaredField("name");
// the name field of the Employee class
Object v = f.get(harry);
// the value of the name field of the harry object, i .e., the String object "Harry Hacker"
}
實際上,上面這段代碼存在一個問題,由于 name 是一個私有域,所以 get() 方法將會拋出一個 illegalAccessException,只有利用 get() 方法才能得到可訪問域的值,除非擁有訪問權限,否則 Java 安全機制只允許査看任意物件有哪些域,而不允許讀取它們的值,
反射機制的默認行為受限于 Java 的訪問控制,然而,如果一個 Java 程式沒有受到安全管理器的控制,就可以覆寫訪問控制,為了達到這個目的,需要呼叫 Field、Method 或 Constructor 物件的 setAccessible() 方法,例如:
f.setAtcessible(true); // now OK to call f.get(harry);
setAccessible() 方法是 AccessibleObject 類中的一個方法,AccessibleObject 類是 Field、Method 和 Constructor 類的公共父類,這個特性是為除錯、持久存盤和相似機制提供的,
呼叫任意方法
在 C 和 C++ 中,可以從函式指標執行任意函式,從表面上看,Java 沒有提供方法指標,即將一個方法的存盤地址傳給另外一個方法,以便第二個方法能夠隨后呼叫它,事實上,Java 的設計者曾說過:方法指標是很危險的,并且常常會帶來隱患,他們認為 Java 提供的介面(interface)是一種更好的解決方案,然而,反射機制允許你呼叫任意方法,
為了能夠看到方法指標的作業程序,先回憶一下利用 Field 類的 get() 方法查看資料域值的程序,與之類似,在 Method 類中有一個 invoke() 方法,它允許呼叫包裝在當前 Method 物件中的方法,
可以使用 method 物件實作 C 語言中函式指標(或 C# 中的委派)的所有操作,同 C 一樣,這種程式設計風格并不太簡便,出錯的可能性也比較大,如果在呼叫方法的時候提供了一個錯誤的引數,那么 invoke() 方法將會拋出一個例外,
另外, invoke() 方法的引數和回傳值必須是 Object 型別的,這就意味著必須進行多次的型別轉換,這樣做將會使編譯器錯過檢查代碼的機會,因此,等到測驗階段才會發現這些錯誤,找到并改正它們將會更加困難,
在進行型別轉換的程序中,編譯器無法檢查代碼中型別轉換的正確性,也就是無法保證轉換后的型別與原始型別是兼容的,這樣就會增加程式出錯的可能性,并且如果出現錯誤的話,除錯和修正也會更加困難,
不僅如此,使用反射獲得方法指標的代碼執行要比直接呼叫方法明顯慢一些,
有鑒于此,建議僅在必要的時候才使用 Method 物件,而最好使用介面以及 Java8 中的 lambda 運算式,
特別要重申:建議 Java 開發者不要使用 Method 物件的回呼功能,使用介面進行回呼會使得代碼的執行速度更快,更易于維護,
參考資料
《Java核心技術卷一:基礎知識》(第10版)第 5 章:繼承 5.7 反射
本文來自博客園,作者:真正的飛魚,轉載請注明原文鏈接:https://www.cnblogs.com/feiyu2/p/17375039.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/551697.html
標籤:Java
下一篇:返回列表
