1 什麼是反射?
反射是一種可以間接操作目標對象的機制。當使用反射時,JVM 在運行的時候才動態加載類,對於任意類,知道其屬性和方法,並不需要提前在編譯期知道運行的對象是誰,允許運行時的 Java 程序獲取類的信息並對其進行操作。
對象的類型在編譯期就可以確定,但程序運行時可能需要動態加載一些類(之前沒有用到,故沒有加載進 jvm),使用反射可以在運行期動態生成對象實例並對其進行操作。
2 反射的原理
在獲取到 Class 對象之後,反向獲取和操作對象的各種信息。
3 反射的使用
我們先建一個類
public class People {
private int age;
private String name;
private People() {
age = 18;
name = "Tony";
}
public People(int age,String name) {
this.age = age;
this.name = name;
}
private void print() {
System.out.println(this.toString());
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
獲得 Class 對象
獲取 Class 對象有三種方法:
- 調用對象的 getClass 方法
- 任何數據類型都擁有的 class 屬性
- 通過 Class 類的靜態方法 forName(String className) 進行調用,該方法最常用
在運行期間,一個類只能有一個 Class 對象產生
獲取類的構造函數
通過 getDeclaredConstructors 方法我們可以得到類的所有構造方法。
public class Test {
public static void main(String[] args) {
Class c = People.class;
//得到類的所有構造方法
Constructor[] constructors = c.getDeclaredConstructors();
for(int i = 0; i < constructors.length; i++) {
//獲得構造方法的類型
System.out.println("構造方法的類型:" + Modifier.toString(constructors[i].getModifiers()));
//獲得構造方法的所有參數
Class[] parametertypes = constructors[i].getParameterTypes();
for (int j = 0; j < parametertypes.length; j++) {
System.out.print(parametertypes[j].getName() + " ");
}
System.out.println("");
}
}
}
返回結果如下:
通過該方法,我們可以獲取類中所有構造方法和構造方法中的參數。
獲取類中特定的構造方法
在 getDeclaredConstructor 方法中,我們未傳入參數,表示希望得到類的特定構造方法。同時在代碼中要進行異常捕獲,因爲可能不存在對應的構造方法。
public class Test {
public static void main(String[] args){
Class c = People.class;
Constructor constructor;
try {
//得到類的特定構造方法,無參構造方法不傳參數
constructor = c.getDeclaredConstructor();
//獲得構造方法的類型
System.out.println("構造方法的類型:" + Modifier.toString(constructor.getModifiers()));
//獲得構造方法的所有參數
System.out.println("構造方法的參數:");
Class[] parametertypes = constructor.getParameterTypes();
for (int j = 0; j < parametertypes.length; j++) {
System.out.print(parametertypes[j].getName() + " ");
}
} catch (Exception e) {}
}
}
結果如下:
如果我們想得到這個構造函數
public People(int age,String name) {
this.age = age;
this.name = name;
}
在 getDeclaredConstructor 方法中,我們可以傳入一個 Class 數組,裏面包含 int 和 java.lang.String 的 Class 對象。
public class Test {
public static void main(String[] args){
Class c = People.class;
Class[] p = {int.class,String.class};
Constructor constructor;
try {
//得到類的特定構造方法,這次傳入int和java.lang.String兩個參數
constructor = c.getDeclaredConstructor(p);
//獲得構造方法的類型
System.out.println("構造方法的類型:" + Modifier.toString(constructor.getModifiers()));
//獲得構造方法的所有參數
System.out.println("構造方法的參數:");
Class[] parametertypes = constructor.getParameterTypes();
for (int j = 0; j < parametertypes.length; j++) {
System.out.print(parametertypes[j].getName() + " ");
}
} catch (Exception e) {}
}
}
結果如下:
調用構造方法
在上面。我們已經學習瞭如何獲取類中特定的構造方法,在這裏,我們不僅要獲取,還要對類的構造方法進行調用。
我們先修改類的兩個構造函數,分別加上一打印語句,代碼如下:
private People() {
age = 18;
name = "Tony";
System.out.println("private People()調用成功");
}
public People(int age,String name) {
this.age = age;
this.name = name;
System.out.println("public People(int age,String name)調用成功");
}
我們先使用這個 public 訪問類型的構造函數
public class Test {
public static void main(String[] args){
Class c = People.class;
Class[] p = {int.class,String.class};
Constructor constructor;
try {
constructor = c.getDeclaredConstructor(p);
//創建實例
constructor.newInstance(10,"HaWei");
} catch (Exception e) {}
}
}
結果如下
那麼我們如何通過反射調用類的 private 訪問類型的構造函數呢?其實大體與上面一樣,只是我們需要設置constructors.setAccessible(true);
罷了。
public class Test {
public static void main(String[] args){
Class c = People.class;
Constructor constructor;
try {
//獲取類的無參構造函數
constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
//創建實例
constructor.newInstance();
} catch (Exception e) {}
}
}
結果如下
調用類的私有方法
我們嘗試調用一下類的這個私有方法
private void print() {
System.out.println(this.toString());
}
關於調用方法,我們可以通過 getDeclaredMethod 來獲取該方法,然後通過調用 invoke 執行。
public class Test {
public static void main(String[] args){
Class c = People.class;
Constructor constructor;
try {
//獲取類的無參構造函數
constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
//創建實例
People obj = (People) constructor.newInstance();
//獲取需要調用的方法,需要兩個參數,第一個參數是方法名,第二個參數是參數類型(本例不需要傳入參數)
Method method = c.getDeclaredMethod("print");
method.setAccessible(true);
//調用方法,需要兩個參數,第一個參數是類的實例,第二個參數是方法參數
method.invoke(obj);
} catch (Exception e) {}
}
}
結果如下:
獲取類的私有字段並修改值
public class Test {
public static void main(String[] args){
Class c = People.class;
Constructor constructor;
try {
//獲取類的無參構造函數
constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
//創建實例
People obj = (People) constructor.newInstance();
//修改之前的name字段值
System.out.println("修改之前:" + obj.getName());
//獲取類的name字段
Field field = c.getDeclaredField("name");
field.setAccessible(true);
//修改類的私有字段
field.set(obj,"HaWei");
//修改之後的name字段值
System.out.println("修改之後:" + obj.getName());
} catch (Exception e) {}
}
}
結果如下:
4 反射的優點
可以在運行時獲得類的內容,對於 Java 這種先編譯再運行的語言,能夠讓我們很方便的寫出靈活的代碼,這些代碼可以在運行時裝配,無需在組件之間進行源代碼的鏈接,更加容易實現面向對象。
5 反射的缺點
- 會消耗一定的系統資源
- 反射調用方法時可以忽略權限檢查,因此可能會破壞封裝性從而導致安全問題
6 反射的用途
- 進行反編譯,把 .class 文件變爲 .java 文件
- 通過反射機制訪問 java 對象的屬性,方法
- 開發各種通用框架(例如 Spring),許多框架都是配置化的,爲了保證框架的通用性,可能需要根據配置文件加載不同的類或者對象,調用不同的方法,這個時候就必須使用到反射了,運行時動態加載需要的加載的對象
- 可以通過反射運行配置文件內容,利用反射和配置文件可以使應用程序更新時,對源碼無需進行任何修改,只需要將新類發送給客戶端,並修改配置文件即可
- 可以通過反射越過泛型檢查,泛型使用在編譯期,編譯過後泛型擦除,反射作用在運行期,所以是可以通過反射越過泛型檢查的