Java 反射機制允許運行時動態檢查類別結構、建構實例、呼叫方法、存取欄位,甚至突破封裝訪問私有成員。本文透過完整程式碼示範反射核心 API 的實戰操作與輸出驗證。
package lavasoft.test;
public class MyService {
private String msg;
public MyService() {
System.out.println("log: 無參建構子被呼叫!");
}
public MyService(String msg) {
System.out.println("log: 有參建構子被呼叫!");
this.msg = msg;
}
public String doSomething(String person, String something) {
System.out.println("log: 業務方法 doSomething 被呼叫!");
return person + " is doing " + something + "! 附加訊息:" + msg;
}
public String getMsg() {
System.out.println("log: getMsg 被呼叫!");
return msg;
}
public void setMsg(String msg) {
System.out.println("log: setMsg 被呼叫!");
this.msg = msg;
}
private String doPrivate() {
System.out.println("log: 私有方法 doPrivate 被呼叫!");
return "私有方法 doPrivate() 被呼叫了!";
}
}
package lavasoft.test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
public class TestReflect {
public static void main(String[] args) throws Exception {
// 1. 取得 Class 物件
Class<MyService> clazz = MyService.class;
// 2. 反射建構物件 (無參建構子)
MyService sv = clazz.getConstructor().newInstance();
sv.setMsg("透過 Class 方式建立物件,並呼叫物件上的方法!");
// 3. 反射呼叫 public 方法
Method mSetMsg = clazz.getMethod("setMsg", String.class);
mSetMsg.invoke(sv, "透過反射呼叫了 sv 物件上的方法!");
Method mDo = clazz.getMethod("doSomething", String.class, String.class);
String result = (String) mDo.invoke(sv, "張三", "吃飯");
System.out.println(result);
// 4. 反射呼叫 private 方法
Method mDoPrivate = clazz.getDeclaredMethod("doPrivate");
mDoPrivate.setAccessible(true); // 關鍵:暴力反射破解封裝
mDoPrivate.invoke(sv);
// 5. 反射存取 private 欄位
Field fMsg = clazz.getDeclaredField("msg");
fMsg.setAccessible(true); // 關鍵:暴力反射破解封裝
fMsg.set(sv, "hahahahahahahaha");
System.out.println(sv.getMsg());
}
}
| 操作 | 方法 | 備註 |
|---|---|---|
| 取得 public 建構子 | clazz.getConstructor(參數型別...) | 拋 NoSuchMethodException |
| 取得任意建構子 | clazz.getDeclaredConstructor(參數型別...) | 含 private,需 setAccessible(true) |
| 建構實例 | constructor.newInstance(參數...) | 拋 InstantiationException/InvocationTargetException |
| 取得 public 方法 | clazz.getMethod(名稱, 參數型別...) | 含繼承自父類的 public 方法 |
| 取得任意方法 | clazz.getDeclaredMethod(名稱, 參數型別...) | 含 private,不含繼承,需 setAccessible(true) |
| 呼叫方法 | method.invoke(實例, 參數...) | 靜態方法實例傳 null |
| 取得 public 欄位 | clazz.getField(名稱) | 含繼承 |
| 取得任意欄位 | clazz.getDeclaredField(名稱) | 含 private,不含繼承,需 setAccessible(true) |
| 設定欄位值 | field.set(實例, 值) | 靜態欄位實例傳 null |
| 讀取欄位值 | field.get(實例) |
log: 無參建構子被呼叫! log: setMsg 被呼叫! log: setMsg 被呼叫! log: 業務方法 doSomething 被呼叫! 張三 is doing 吃飯! 附加訊息:透過反射呼叫了 sv 物件上的方法! log: 私有方法 doPrivate 被呼叫了! log: getMsg 被呼叫! hahahahahahahaha Process finished with exit code 0
| 考量點 | 說明 | 優化建議 |
|---|---|---|
| 效能開銷 | 反射比直接呼叫慢 10-100 倍 (JIT 優化後縮小) | 緩存 Constructor/Method/Field 物件、使用 MethodHandle (Java 7+) |
| 封裝破壞 | setAccessible(true) 可繞過 private/protected | SecurityManager 限制、模組化 (Java 9+) 強制封裝 |
| 異常處理 | 檢查型異常多 (InvocationTargetException 等) | 統一包裝為非檢查異常、使用 Lombok @SneakyThrows |
| 泛型擦除 | 運行時無法取得泛型參數實際類型 | 使用 TypeToken (Gson) 或 ParameterizedType 解析 |
// Java 9+ 需在 module-info.java 開放反射存取
module lavasoft.test {
opens lavasoft.test to java.base, java.lang.reflect; // 允許反射存取私有成員
// 或 opens lavasoft.test; // 開放給所有模組
}
未開放時 setAccessible(true) 會拋 InaccessibleObjectException。
getCause() 中