Java反射機制是 Java 語言的一個重要特性。
在學習 Java 反射機制前,大家應該先了解兩個概念,編譯期和運行期。
編譯期是指把源碼交給編譯器編譯成計算機可以執行的文件的過程。在 Java 中也就是把 Java 代碼編成 class 文件的過程。編譯期只是做了一些翻譯功能,并沒有把代碼放在內存中運行起來,而只是把代碼當成文本進行操作,比如檢查錯誤。
運行期是把編譯后的文件交給計算機執行,直到程序運行結束。所謂運行期就把在磁盤中的代碼放到內存中執行起來。
反射機制是什么?
Java反射機制是指在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法。
對于任意一個對象,都能夠調用它的任意一個方法和屬性,這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。
用一句話總結就是反射可以實現在運行時可以知道任意一個類的屬性和方法。
為什么要用反射?
Java 反射機制主要提供了以下功能,這些功能都位于java.lang.reflect
包比如:
Java 反射機制主要包括以下幾個方面:
- 獲取類的信息:反射機制允許程序在運行時獲取一個類的信息,例如:類的名稱、修飾符、父類、接口、屬性和方法等。
- 創建對象:通過反射機制,程序可以在運行時創建一個類的實例對象,而不需要在編譯時知道這個類的類型。
- 訪問屬性:通過反射機制,程序可以在運行時訪問一個對象的屬性,例如:獲取屬性值、設置屬性值、獲取屬性的類型和修飾符等。
- 調用方法:通過反射機制,程序可以在運行時調用一個對象的方法,例如:獲取方法的參數、調用方法、獲取方法的返回值等。
- 修改訪問權限:通過反射機制,程序可以在運行時修改對象的訪問權限,例如:設置屬性或方法的訪問權限為 public、private 或 protected。
Java 反射機制在實際應用中有廣泛的用途,例如:框架、動態代理、注解處理器等。反射機制雖然功能強大,但是也有一些缺點,例如:性能較低、安全性較差等,因此在使用時需要注意。
要想知道一個類的屬性和方法,必須先獲取到該類的字節碼文件對象。獲取類的信息時,使用的就是 Class 類中的方法。所以先要獲取到每一個字節碼文件(.class)對應的 Class 類型的對象。
眾所周知,所有 Java 類均繼承了 Object 類,在 Object 類中定義了一個 getClass() 方法,該方法返回同一個類型為 Class 的對象。
表列出了通過反射可以訪問的信息。
類型 | 訪問方法 | 返回值類型 | 說明 |
---|---|---|---|
包路徑 | getPackage() | Package?對象 | 獲取該類的存放路徑 |
類名稱 | getName() | String 對象 | 獲取該類的名稱 |
繼承類 | getSuperclass() | Class 對象 | 獲取該類繼承的類 |
實現接口 | getlnterfaces() | Class 型數組 | 獲取該類實現的所有接口 |
構造方法 | getConstructors() | Constructor?型數組 | 獲取所有權限為 public 的構造方法 |
getDeclaredContruectors() | Constructor?對象 | 獲取當前對象的所有構造方法 | |
方法 | getMethods() | Methods 型數組 | 獲取所有權限為 public 的方法 |
getDeclaredMethods() | Methods?對象 | 獲取當前對象的所有方法 | |
成員變量 | getFields() | Field 型數組 | 獲取所有權限為 public 的成員變量 |
getDeclareFileds() | Field 對象 | 獲取當前對象的所有成員變量 | |
內部類 | getClasses() | Class 型數組 | 獲取所有權限為 public 的內部類 |
getDeclaredClasses() | Class 型數組 | 獲取所有內部類 | |
內部類的聲明類 | getDeclaringClass() | Class 對象 | 如果該類為內部類,則返回它的成員類,否則返回 null |
反射機制的優缺點
優點:
- 能夠運行時動態獲取類的實例,大大提高系統的靈活性和擴展性。
- 與 Java 動態編譯相結合,可以實現無比強大的功能。
- 對于 Java 這種先編譯再運行的語言,能夠讓我們很方便的創建靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代碼的鏈接,更加容易實現面向對象。
缺點:
- 反射會消耗一定的系統資源,因此,如果不需要動態地創建一個對象,那么就不需要用反射;
- 反射調用方法時可以忽略權限檢查,獲取這個類的私有方法和屬性,因此可能會破壞類的封裝性而導致安全問題。
?反射機制API
java.lang.Class 類
java.lang.Class 類是實現反射的關鍵所在,Class 類的一個實例表示 Java 的一種數據類型,包括類、接口、枚舉、注解(Annotation)、數組、基本數據類型和 void。Class 沒有公有的構造方法,Class 實例是由 JVM 在類加載時自動創建的。
在程序代碼中獲得 Class 實例可以通過如下代碼實現:
// 1. 通過類型class靜態變量
Class clz1 = String.class;
String str = "Hello";
// 2. 通過對象的getClass()方法
Class clz2 = str.getClass();
每一種類型包括類和接口等,都有一個 class 靜態變量可以獲得 Class 實例。另外,每一個對象都有 getClass() 方法可以獲得 Class 實例,該方法是由 Object 類提供的實例方法。
Class 類提供了很多方法可以獲得運行時對象的相關信息,下面的程序代碼展示了其中一些方法。
public class ReflectionTest01 {public static void main(String[] args) {// 獲得Class實例// 1.通過類型class靜態變量Class clz1 = String.class;String str = "Hello";// 2.通過對象的getClass()方法Class clz2 = str.getClass();// 獲得int類型Class實例Class clz3 = int.class;// 獲得Integer類型Class實例Class clz4 = Integer.class;System.out.println("clz2類名稱:" + clz2.getName());System.out.println("clz2是否為接口:" + clz2.isInterface());System.out.println("clz2是否為數組對象:" + clz2.isArray());System.out.println("clz2父類名稱:" + clz2.getSuperclass().getName());System.out.println("clz2是否為基本類型:" + clz2.isPrimitive());System.out.println("clz3是否為基本類型:" + clz3.isPrimitive());System.out.println("clz4是否為基本類型:" + clz4.isPrimitive());}
}
運行結果如下:
clz2類名稱:java.lang.String
clz2是否為接口:false
clz2是否為數組對象:false
clz2父類名稱:java.lang.Object
clz2是否為基本類型:false
clz3是否為基本類型:true
clz4是否為基本類型:false
java.lang.reflect 包
java.lang.reflect 包提供了反射中用到類,主要的類說明如下:
- Constructor 類:提供類的構造方法信息。
- Field 類:提供類或接口中成員變量信息。
- Method 類:提供類或接口成員方法信息。
- Array 類:提供了動態創建和訪問 Java 數組的方法。
- Modifier 類:提供類和成員訪問修飾符信息。
示例代碼如下:
public class ReflectionTest02 {public static void main(String[] args) {try {// 動態加載xx類的運行時對象Class c = Class.forName("java.lang.String");// 獲取成員方法集合Method[] methods = c.getDeclaredMethods();// 遍歷成員方法集合for (Method method : methods) {// 打印權限修飾符,如public、protected、privateSystem.out.print(Modifier.toString(method.getModifiers()));// 打印返回值類型名稱System.out.print(" " + method.getReturnType().getName() + " ");// 打印方法名稱System.out.println(method.getName() + "();");}} catch (ClassNotFoundException e) {System.out.println("找不到指定類");}}
}
通過反射訪問構造方法
為了能夠動態獲取對象構造方法的信息,首先需要通過下列方法之一創建一個 Constructor
類型的對象或者數組。
- getConstructors()
- getConstructor(Class<?>…parameterTypes)
- getDeclaredConstructors()
- getDeclaredConstructor(Class<?>...parameterTypes)
如果是訪問指定的構造方法,需要根據該構造方法的入口參數的類型來訪問。例如,訪問一個入口參數類型依次為 int 和 String 類型的構造方法,下面的兩種方式均可以實現。
1、objectClass.getDeclaredConstructor(int.class,String.class);
2、objectClass.getDeclaredConstructor(new Class[]{int.class,String.class});
創建的每個 Constructor 對象表示一個構造方法,然后利用 Constructor 對象的方法操作構造方法。Constructor 類的常用方法如表所示。
方法名稱 | 說明 |
---|---|
isVarArgs() | 查看該構造方法是否允許帶可變數量的參數,如果允許,返回 true,否則返回 false |
getParameterTypes() | 按照聲明順序以 Class 數組的形式獲取該構造方法各個參數的類型 |
getExceptionTypes() | 以 Class 數組的形式獲取該構造方法可能拋出的異常類型 |
newInstance(Object … initargs) | 通過該構造方法利用指定參數創建一個該類型的對象,如果未設置參數則表示 采用默認無參的構造方法 |
setAccessiable(boolean flag) | 如果該構造方法的權限為 private,默認為不允許通過反射利用?netlnstance() 方法創建對象。如果先執行該方法,并將入口參數設置為?true,則允許創建對 象 |
getModifiers() | 獲得可以解析出該構造方法所采用修飾符的整數 |
通過 java.lang.reflect.Modifier 類可以解析出 getMocMers() 方法的返回值所表示的修飾符信息。在該類中提供了一系列用來解析的靜態方法,既可以查看是否被指定的修飾符修飾,還可以字符串的形式獲得所有修飾符。表列出了 Modifier 類的常用靜態方法。
靜態方法名稱 | 說明 | |
---|---|---|
isStatic(int mod) | 如果使用 static 修飾符修飾則返回 true,否則返回 false | |
isPublic(int mod) | 如果使用 public 修飾符修飾則返回 true,否則返回 false | |
isProtected(int mod) | 如果使用 protected 修飾符修飾則返回 true,否則返回 false | |
isPrivate(int mod) | 如果使用 private 修飾符修飾則返回 true,否則返回 false | |
isFinal(int mod) | 如果使用 final 修飾符修飾則返回 true,否則返回 false | |
toString(int mod) | 以字符串形式返回所有修飾符 |
例如,下列代碼判斷對象 con 所代表的構造方法是否被 public 修飾,以及以字符串形式獲取該構造方法的所有修飾符。
int modifiers = con.getModifiers(); // 獲取構造方法的修飾符整數
boolean isPublic = Modifier.isPublic(modifiers); // 判斷修飾符整數是否為public
string allModifiers = Modifier.toString(modifiers);
通過反射執行方法(獲取方法)
要動態獲取一個對象方法的信息,首先需要通過下列方法之一創建一個 Method
類型的對象或者數組。
- getMethods()
- getMethods(String name,Class<?> …parameterTypes)
- getDeclaredMethods()
- getDeclaredMethods(String name,Class<?>...parameterTypes)
如果是訪問指定的構造方法,需要根據該方法的入口參數的類型來訪問。例如,訪問一個名稱為 max,入口參數類型依次為 int 和 String 類型的方法。
下面的兩種方式均可以實現:
1、objectClass.getDeclaredConstructor("max",int.class,String.class);
2、objectClass.getDeclaredConstructor("max",new Class[]{int.class,String.class});
Method 類的常用方法如表 3 所示。
靜態方法名稱 | 說明 |
---|---|
getName() | 獲取該方法的名稱 |
getParameterType() | 按照聲明順序以 Class 數組的形式返回該方法各個參數的類型 |
getReturnType() | 以 Class 對象的形式獲得該方法的返回值類型 |
getExceptionTypes() | 以 Class 數組的形式獲得該方法可能拋出的異常類型 |
invoke(Object obj,Object...args) | 利用 args 參數執行指定對象 obj 中的該方法,返回值為 Object 類型 |
isVarArgs() | 查看該方法是否允許帶有可變數量的參數,如果允許返回 true,否則返回 false |
getModifiers() | 獲得可以解析出該方法所采用修飾符的整數 |
通過反射訪問成員變量?
通過下列任意一個方法訪問成員變量時將返回 Field 類型的對象或數組。
- getFields()
- getField(String name)
- getDeclaredFields()
- getDeclaredField(String name)
上述方法返回的 Field 對象代表一個成員變量。例如,要訪問一個名稱為 price 的成員變量,示例代碼如下:
object.getDeciaredField("price");
Field 類的常用方法如表所示
方法名稱 | 說明 |
---|---|
getName() | 獲得該成員變量的名稱 |
getType() | 獲取表示該成員變量的 Class 對象 |
get(Object obj) | 獲得指定對象 obj 中成員變量的值,返回值為 Object 類型 |
set(Object obj, Object value) | 將指定對象 obj 中成員變量的值設置為 value |
getlnt(0bject obj) | 獲得指定對象 obj 中成員類型為 int 的成員變量的值 |
setlnt(0bject obj, int i) | 將指定對象 obj 中成員變量的值設置為 i |
setFloat(Object obj, float f) | 將指定對象 obj 中成員變量的值設置為 f |
getBoolean(Object obj) | 獲得指定對象 obj 中成員類型為 boolean 的成員變量的值 |
setBoolean(Object obj, boolean b) | 將指定對象 obj 中成員變量的值設置為 b |
getFloat(Object obj) | 獲得指定對象 obj 中成員類型為 float 的成員變量的值 |
setAccessible(boolean flag) | 此方法可以設置是否忽略權限直接訪問 private 等私有權限的成員變量 |
getModifiers() | 獲得可以解析出該方法所采用修飾符的整數 |