一文講透 Java 中的反射
什么是反射
反射是 Java 中的一個特性,它允許程序在運(yùn)行時獲取自身的信息,并動態(tài)地操作類或?qū)ο蟮膶傩?、方法和?gòu)造函數(shù)。通過反射,我們可以在事先不知道確切類名的情況下實例化對象、調(diào)用方法和設(shè)置屬性。
反射機(jī)制的核心是Class對象,它代表一個類。Java 虛擬機(jī)(JVM)在加載類時會自動創(chuàng)建這個Class對象。
JVM 如何創(chuàng)建一個類
當(dāng)我們編寫一個類并進(jìn)行編譯時,編譯器會將其轉(zhuǎn)換為存儲在.class文件中的字節(jié)碼。在類加載過程中,JVM 使用ClassLoader讀取.class文件,將字節(jié)碼加載到內(nèi)存中,并根據(jù)這些信息創(chuàng)建相應(yīng)的Class對象。由于每個類在 JVM 中只加載一次,所以每個類都對應(yīng)一個唯一的Class對象。
示例:
public class User extends People {
public String name;
private int age;
private static int staticFiled = 10;
private final String sex;
protected String protectedFiled;
static {
System.out.println("靜態(tài)方法執(zhí)行");
}
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
private void privateMethod() {
System.out.println("我是私有方法");
}
public void publicMethod() {
System.out.println("我是公共方法");
}
}
public class People {
public String publicFiled;
private String privateFiled;
}
獲取Class對象的三種方式
(1) 第一種方法通過類名使用.class獲取類對象。這是在編譯時完成的,所以明確指定了類型User,不會導(dǎo)致任何錯誤。使用這種方法獲取對象不會觸發(fā)類初始化;只有在訪問類的靜態(tài)成員或?qū)嵗龝r才會進(jìn)行初始化。
Class<User> userClass = User.class;
實例化一個對象:
User userInstance = userClass.getDeclaredConstructor(String.class, String.class).newInstance("張三", "男");
(2) 第二種方法通過對象的getClass()方法獲取類對象。這種方法適用于從已實例化的類對象中獲取類對象。請注意,類型不是User,而是通配符?,因為Class對象是從User的實例中獲取的,實例的具體類型只能在運(yùn)行時確定,而不是在編譯時。
User user = new User("張三", "男");
Class<?> userClass = user.getClass();
實例化一個對象:
Constructor<?> constructor = userClass.getConstructor(String.class, String.class);
User userInstance = (User) constructor.newInstance("張三", "男");
(3) 第三種方法使用靜態(tài)方法Class.forName()通過全路徑獲取類對象。由于類型只能在運(yùn)行時知道,所以類型是通配符?。通過這種方法獲取類對象將立即觸發(fā)類初始化。
Class<?> userClass = Class.forName("org.example.reflect.entity.User");
創(chuàng)建一個實例:
Constructor<?> constructor = userClass.getDeclaredConstructor(String.class, String.class);
User userInstance = (User) constructor.newInstance("張三", "男");
在 Java 中訪問對象字段
獲取所有公共字段要獲取所有公共字段,包括從父類繼承的字段,使用getFields():
Field[] fields = user.getFields();
for (Field field : fields) {
System.out.println(field);
}
輸出:
public java.lang.String org.example.reflect.entity.User.name
public java.lang.String org.example.reflect.entity.People.publicField
(2) 獲取所有聲明的字段要獲取類中所有聲明的字段,無論其訪問級別如何,使用getDeclaredFields()。這不包括從超類繼承的字段:
Field[] fields = user.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
輸出:
public java.lang.String org.example.reflect.entity.User.name
private int org.example.reflect.entity.User.age
private final java.lang.String org.example.reflect.entity.User.sex
protected java.lang.String org.example.reflect.entity.User.protectedField
(3) 獲取超類中的字段要獲取超類中的字段,使用getSuperclass():
Field[] fields = user.getSuperclass().getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
輸出:
public java.lang.String org.example.reflect.entity.People.publicField
private java.lang.String org.example.reflect.entity.People.privateField
(4) 獲取特定字段要通過名稱獲取特定公共字段,使用getField(String name)。對于任何特定字段,無論其訪問級別如何,使用getDeclaredField(String name)。
(5) 處理不存在的字段嘗試訪問不存在的字段不會產(chǎn)生編譯時錯誤,但會在運(yùn)行時拋出異常:
try {
Field nonExistentField = user.getDeclaredField("nonExistentField");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
輸出:
java.lang.NoSuchFieldException: nonExistentField
(6) 設(shè)置字段值要設(shè)置私有靜態(tài)字段的值,首先使其可訪問:
Class<?> userClass = Class.forName("org.example.reflect.entity.User");
Field staticField = userClass.getDeclaredField("staticField");
staticField.setAccessible(true);
System.out.println(staticField.get(null));
如果字段是final的,仍然可以修改它:
Field field = userClass.getDeclaredField("sex");
field.setAccessible(true);
field.set(obj, "女生");
System.out.println(field.get(obj));
輸出:
女生
訪問方法
訪問方法與訪問字段類似:
- getMethods()檢索類及其超類中的所有公共方法。
- getDeclaredMethods()檢索類中所有聲明的方法,無論訪問級別如何。
- getMethod(String name, Class<?>... parameterTypes)按名稱和參數(shù)類型檢索特定公共方法。
- getDeclaredMethod(String name, Class<?>... parameterTypes)按名稱和參數(shù)類型檢索特定聲明的方法,無論訪問級別如何。
總結(jié)
從上面的示例中可以看出,以Declared為前綴的方法(如getDeclaredField)用于檢索所有字段或方法,無論其訪問級別如何。相比之下,沒有Declared的方法(如getField)僅檢索公共字段或方法。
反射允許繞過訪問控制檢查。用private或final修飾的字段和方法可以被訪問和修改,這破壞了封裝性。因此,應(yīng)該謹(jǐn)慎使用。