首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 反射機制核心原理與實戰:從 Class 對象到泛型擦除驗證

     編輯:Java 綜合教學

前言:運行時的「X 光機」

Java 反射允許程序在運行狀態下,對任意類獲取完整結構信息(字段、方法、構造器),並能動態創建實例、調用方法、訪問屬性。這是框架開發(Spring、Hibernate、MyBatis)、動態代理、注解處理器的基石。本文從 `Class` 對象本質講起,逐步深入到成員訪問、動態加載、泛型擦除驗證,並附完整可運行代碼。

一、Class 類與類類型:反射的基石

1.1 類也是對象嗎?

是的。「類是 `java.lang.Class` 的實例」,`Class` 是所有類的類。JVM 為每個加載的類創建唯一一個 `Class` 對象,封裝了該類的完整元數據。

1.2 三種獲取 Class 對象的方式

// 方式 1:類字面常量(編譯期已知,最安全、性能最好)
Class c1 = Code.class;

// 方式 2:實例 getClass()(運行期實例已知)
Code code1 = new Code();
Class c2 = code1.getClass();

// 方式 3:全限定名動態加載(配置驅動、插件化核心)
Class c3 = Class.forName("com.trigl.reflect.Code"); // 拋 ClassNotFoundException

// 驗證:三者指向同一 Class 對象
System.out.println(c1 == c2 && c2 == c3); // true

1.3 類類型 = 反射入口

`Class` 對象即「類類型」。通過它可:

  • `newInstance()` 創建實例(需無參構造器,Java 9+ 推薦 `getDeclaredConstructor().newInstance()`)
  • `getDeclaredMethods()` / `getMethods()` 獲取方法
  • `getDeclaredFields()` / `getFields()` 獲取字段
  • `getDeclaredConstructors()` 獲取構造器

二、動態加載類:插件化架構的核心

2.1 靜態 vs 動態加載

  • 靜態:編譯期依賴,`new Word()` 需 `Word.class` 存在,否則編譯失敗。
  • 動態:`Class.forName(args[0])` 運行期根據字符串加載,配合接口實現多態解耦。

2.2 實戰:Office 套件啟動器

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

三、獲取類完整結構信息

3.1 成員方法(`java.lang.reflect.Method`)

// 單個方法
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();
}

3.2 成員變量(`java.lang.reflect.Field`)

// 單個字段(含 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());
}

3.3 構造器(`java.lang.reflect.Constructor`)

Constructor[] ctors = clazz.getDeclaredConstructors();
for (Constructor ctor : ctors) {
    Class[] pTypes = ctor.getParameterTypes();
    // 可動態選擇構造器實例化
}

四、反射揭秘:集合泛型的本質——擦除

4.1 結論先行

Java 泛型僅在編譯期有效(類型檢查),運行期擦除為原始類型。反射可繞過編譯器,向 `List` 插入 `Integer`。

4.2 驗證代碼

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 對象相同

4.3 核心證據

  • `List` 和 `List` 共用同一 `Class` 對象(`ArrayList.class`)。
  • 字節碼中無泛型信息,僅在 `Signature` 屬性保留給編譯器讀取。

五、反射最佳實踐與避坑指南

場景建議
高頻調用緩存 `Method`/`Field`/`Constructor` 對象;考慮 `MethodHandle`/`VarHandle` (Java 9+)
訪問 private慎用 `setAccessible(true)`,模塊化系統 (JPMS) 可能禁止;優先提供公共 API
性能敏感反射比直接調用慢 10-100 倍;熱路徑用代碼生成 或 Lambda 元工廠
異常處理捕獲 `InvocationTargetException` 並 `.getCause()` 獲取真實業務異常
安全管理器舊版 SecurityManager 可限制反射;現代項目少用

總結

反射賦予了 Java 「運行時自我認知與操作」的動態能力,是框架級開發的利器。但濫用反射會破壞封裝、降低性能、增加維護難度。掌握 `Class` 對象獲取、成員訪問、動態加載三大核心,理解泛型擦除本質,並在業務代碼中克制使用、框架代碼中善用緩存與替代方案,才是反射的正確打開方式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved