Java 反射允許程序在運行狀態下,對任意類獲取完整結構信息(字段、方法、構造器),並能動態創建實例、調用方法、訪問屬性。這是框架開發(Spring、Hibernate、MyBatis)、動態代理、注解處理器的基石。本文從 `Class` 對象本質講起,逐步深入到成員訪問、動態加載、泛型擦除驗證,並附完整可運行代碼。
是的。「類是 `java.lang.Class` 的實例」,`Class` 是所有類的類。JVM 為每個加載的類創建唯一一個 `Class` 對象,封裝了該類的完整元數據。
// 方式 1:類字面常量(編譯期已知,最安全、性能最好)
Class c1 = Code.class;
// 方式 2:實例 getClass()(運行期實例已知)
Code code1 = new Code();
Class extends Code> c2 = code1.getClass();
// 方式 3:全限定名動態加載(配置驅動、插件化核心)
Class> c3 = Class.forName("com.trigl.reflect.Code"); // 拋 ClassNotFoundException
// 驗證:三者指向同一 Class 對象
System.out.println(c1 == c2 && c2 == c3); // true
`Class` 對象即「類類型」。通過它可:
interface OfficeAble { void start(); }
class Word implements OfficeAble {
public void start() { System.out.println("Word...starts..."); }
}
class Excel implements OfficeAble {
public void start() { System.out.println("Excel...starts..."); }
}
public class OfficeBetter {
public static void main(String[] args) {
try {
// args[0] 如 "Word" 或 "Excel"
Class> c = Class.forName(args[0]);
OfficeAble oa = (OfficeAble) c.getDeclaredConstructor().newInstance();
oa.start();
} catch (Exception e) { e.printStackTrace(); }
}
}
// 新增 PPT 只需實現 OfficeAble,無需修改 OfficeBetter
// 單個方法
Method m = clazz.getDeclaredMethod("print", String.class, int.class);
m.invoke(obj, "hello", 10); // 相當於 obj.print("hello", 10)
// 所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("方法名: " + method.getName());
System.out.println("返回類型: " + method.getReturnType().getName());
Class>[] params = method.getParameterTypes();
for (Class> p : params) System.out.print(p.getName() + " ");
System.out.println();
}
// 單個字段(含 private)
Field f = clazz.getDeclaredField("name");
f.setAccessible(true); // 關鍵:暴力反射破壞封裝
f.set(obj, "new value");
// 所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + " : " + field.getType().getName());
}
Constructor>[] ctors = clazz.getDeclaredConstructors();
for (Constructor> ctor : ctors) {
Class>[] pTypes = ctor.getParameterTypes();
// 可動態選擇構造器實例化
}
Java 泛型僅在編譯期有效(類型檢查),運行期擦除為原始類型。反射可繞過編譯器,向 `List` 插入 `Integer`。
List list = new ArrayList<>();
list.add("hello");
// list.add(20); // 編譯報錯
Class> c = list.getClass();
Method add = c.getMethod("add", Object.class);
add.invoke(list, 20); // 繞過編譯器,運行期插入成功
System.out.println(list.size()); // 2
System.out.println(list.get(1)); // 20 (Integer)
System.out.println(list.getClass() == new ArrayList().getClass()); // true,Class 對象相同
| 場景 | 建議 |
|---|---|
| 高頻調用 | 緩存 `Method`/`Field`/`Constructor` 對象;考慮 `MethodHandle`/`VarHandle` (Java 9+) |
| 訪問 private | 慎用 `setAccessible(true)`,模塊化系統 (JPMS) 可能禁止;優先提供公共 API |
| 性能敏感 | 反射比直接調用慢 10-100 倍;熱路徑用代碼生成 或 Lambda 元工廠 |
| 異常處理 | 捕獲 `InvocationTargetException` 並 `.getCause()` 獲取真實業務異常 |
| 安全管理器 | 舊版 SecurityManager 可限制反射;現代項目少用 |
反射賦予了 Java 「運行時自我認知與操作」的動態能力,是框架級開發的利器。但濫用反射會破壞封裝、降低性能、增加維護難度。掌握 `Class` 對象獲取、成員訪問、動態加載三大核心,理解泛型擦除本質,並在業務代碼中克制使用、框架代碼中善用緩存與替代方案,才是反射的正確打開方式。