Java Reflection(反射)API 允許程式在執行期間動態地取得類別資訊、建立物件實例,以及呼叫方法,而不需要在編譯時期知道類別的名稱。這在框架開發、外掛系統、序列化等場景非常有用。
取得 Class 物件
有三種方式可以取得 Class 物件:
1
2
3
4
5
6
7
8
9
|
// 方法一:透過類別名稱字串(最常用於動態載入)
Class<?> clazz = Class.forName("com.example.MyClass");
// 方法二:透過類別的 .class 語法(編譯時期已知)
Class<?> clazz = MyClass.class;
// 方法三:透過物件實例
MyClass obj = new MyClass();
Class<?> clazz = obj.getClass();
|
動態建立物件實例
使用無參數建構子
1
2
3
4
5
6
7
|
Class<?> clazz = Class.forName("com.example.MyClass");
// 方法一(Java 9+ 已棄用,但仍可用)
Object instance = clazz.newInstance();
// 方法二(建議使用)
Object instance = clazz.getDeclaredConstructor().newInstance();
|
使用有參數的建構子
1
2
3
4
5
6
7
|
Class<?> clazz = Class.forName("com.example.Person");
// 取得接受 String 和 int 參數的建構子
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 建立實例
Object person = constructor.newInstance("小明", 25);
|
動態呼叫方法
1
2
3
4
5
6
7
8
9
|
Class<?> clazz = Class.forName("com.example.Calculator");
Object instance = clazz.getDeclaredConstructor().newInstance();
// 取得方法(方法名稱 + 參數類型)
Method method = clazz.getMethod("add", int.class, int.class);
// 呼叫方法(instance, 參數列表)
Object result = method.invoke(instance, 10, 20);
System.out.println(result); // 30
|
呼叫私有方法
預設無法存取私有方法,需要使用 setAccessible(true) 解除存取限制:
1
2
3
|
Method privateMethod = clazz.getDeclaredMethod("privateCalculate", int.class);
privateMethod.setAccessible(true); // 解除私有限制
Object result = privateMethod.invoke(instance, 42);
|
讀寫欄位(Field)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Class<?> clazz = Class.forName("com.example.Person");
Object person = clazz.getDeclaredConstructor().newInstance();
// 取得欄位
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 若為 private 需要設定
// 寫入值
nameField.set(person, "小明");
// 讀取值
String name = (String) nameField.get(person);
System.out.println(name); // 小明
|
完整範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import java.lang.reflect.*;
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 動態載入類別
Class<?> clazz = Class.forName("com.example.Calculator");
// 建立實例
Object calc = clazz.getDeclaredConstructor().newInstance();
// 呼叫 add 方法
Method addMethod = clazz.getMethod("add", int.class, int.class);
int result = (int) addMethod.invoke(calc, 5, 3);
System.out.println("5 + 3 = " + result); // 8
// 列出所有公開方法
for (Method m : clazz.getMethods()) {
System.out.println("方法:" + m.getName());
}
}
}
|
應用場景
- 框架開發:Spring 的 DI 容器、MyBatis 的 ORM 映射
- 外掛系統:根據設定檔動態載入類別
- 序列化/反序列化:Jackson、Gson 在執行期間讀寫物件欄位
- 單元測試:測試私有方法或欄位
效能注意事項
Reflection 比直接呼叫方法慢約 10~50 倍(視情況而定),原因包括:
- 需要進行安全性檢查
- 無法被 JIT 編譯器最佳化
- 需要動態解析類型資訊
在效能敏感的程式碼路徑中,應快取 Method、Field 等物件,避免重複取得。若需要大量動態呼叫,可考慮改用 MethodHandle(Java 7+)或 ByteBuddy 等位元組碼生成工具。
參考資料