整理 Effective Java 書中 Item 65: Prefer interfaces to reflection 心得筆記

主旨

反射(Reflection)是 Java 提供的一項強大功能,讓程式在執行期間也能動態存取類別的建構子、方法、欄位等資訊。不過,反射的代價非常高:

  • 沒有編譯期型別檢查
  • 需要大量樣板代碼
  • 效能遠低於直接呼叫

如果可以,應該優先考慮使用介面或超類別來取代反射的存取方式


反射的問題點

缺點一:失去型別安全

反射繞過了 Java 的型別系統。錯誤不會在編譯期出現,而是在執行期爆炸,例如:

Method m = someObject.getClass().getMethod("doSomething");
m.invoke(someObject); // 若方法不存在會在 runtime 爆錯

缺點二:程式碼難讀又冗長

要用反射寫個建構子呼叫就要一堆 try-catch,沒幾行有意義的程式。

缺點三:效能差

反射呼叫一個空參數的方法,在某些機器上會慢 10 倍以上。如果你在迴圈中大量用反射,會讓效能爆炸下降。


範例:反射 + 介面存取的做法

以下是一段用反射動態建立 Set 的範例(根據參數建立 HashSet 或 TreeSet):

// 用反射載入 Set 實作類別
Class<? extends Set<String>> cl = 
    (Class<? extends Set<String>>) Class.forName(args[0]);

Constructor<? extends Set<String>> cons = cl.getDeclaredConstructor();
Set<String> s = cons.newInstance();

// 用介面操作物件
s.addAll(Arrays.asList(args).subList(1, args.length));
System.out.println(s);

✅ 雖然使用了反射,但建立完物件後,透過介面 Set 來操作資料,讓主程式碼邏輯簡單又清晰。


真實應用:多版本支援

反射有一種罕見但合理的用途是跨版本兼容。例如你開發一個工具,需要同時支援 Java 8 與 Java 11,但某些 API 在 Java 11 才有。這時可以:

  1. 用最舊版本編譯(如 Java 8)
  2. 執行時動態偵測有沒有 Java 11 的新方法
  3. 如果有,用反射呼叫;沒有就降級處理

這種做法可以讓你的程式碼在不同 JDK 上都能跑。


小結:什麼時候才該用反射?

使用時機建議行為
不知道類別是誰,但有共同介面/超類別✅ 用反射建立,用介面操作
需要動態載入外部 plugin/module✅ 只用反射處理初始化階段
工具框架(如 DI、測試工具)✅ 有需要但應設計良好封裝反射
可以用介面、enum、泛型替代的情境❌ 避免反射,保持可讀性與型別安全

✅ 結論:能不用反射就不用反射。必要時也僅限在初始化階段,之後一律改用介面來操作物件,這樣才能兼顧彈性與穩定。