Contents

Java Class 動態建立(new)與呼叫方法(call Method)

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 倍(視情況而定),原因包括:

  1. 需要進行安全性檢查
  2. 無法被 JIT 編譯器最佳化
  3. 需要動態解析類型資訊

在效能敏感的程式碼路徑中,應快取 MethodField 等物件,避免重複取得。若需要大量動態呼叫,可考慮改用 MethodHandle(Java 7+)或 ByteBuddy 等位元組碼生成工具。

參考資料