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

Java 反射機制全面解析:從入門到實戰 (類加載、動態創建、泛型擦除、代理模式、工廠模式重構)

     編輯:Java 相關知識

前言

反射是 Java 動態性的核心,允許運行時檢查類結構、創建對象、調用方法、操作字段、生成動態代理。本文通過 15+ 實戰案例,系統講解反射核心 API、類加載機制、動態代理、工廠模式重構、數組操作等進階主題。

一、Class 對象:反射的入口

1. 三種獲取方式

Class c1 = Class.forName("pkg.ClassName");     // 最常用,字符串配置
Class c2 = new MyClass().getClass();           // 實例獲取
Class c3 = MyClass.class;                      // 字面量,編譯期檢查

所有類的對象都是 Class 的實例,JVM 為每個類型創建唯一的 Class 對象。

2. 獲取類信息

System.out.println(c1.getName());        // 完整類名 pkg.ClassName
System.out.println(c1.getSimpleName());  // 簡單類名 ClassName
System.out.println(c1.getPackage());     // Package 對象
System.out.println(c1.getSuperclass());  // 父類 Class
System.out.println(Arrays.toString(c1.getInterfaces())); // 實現的接口數組
System.out.println(c1.getModifiers());   // 修飾符 (public/abstract/final 等)

二、動態創建對象

1. 無參構造器

Object obj = clazz.newInstance(); // 已棄用 Java 9
// 推薦
Constructor cons = clazz.getDeclaredConstructor();
cons.setAccessible(true); // 破壞私有
Object obj = cons.newInstance();

2. 帶參構造器

Constructor cons = clazz.getDeclaredConstructor(String.class, int.class);
cons.setAccessible(true);
Object obj = cons.newInstance("Rollen", 20);

3. 獲取所有構造器

for (Constructor c : clazz.getDeclaredConstructors()) {
    System.out.println(Modifier.toString(c.getModifiers()) + " " + c.getName() + Arrays.toString(c.getParameterTypes()));
}

三、動態調用方法

1. 獲取方法

Method m = clazz.getDeclaredMethod("methodName", String.class, int.class);
m.setAccessible(true);
Object result = m.invoke(obj, "arg1", 100); // 實例方法傳 obj,靜態方法傳 null

2. 獲取所有方法 (含繼承)


for (Method m : clazz.getMethods()) { // 只獲取 public,含繼承
    System.out.println(Modifier.toString(m.getModifiers()) + " " + m.getReturnType().getSimpleName() + " " + m.getName() + Arrays.toString(m.getParameterTypes()));
}

3. 獲取聲明方法 (含私有)


for (Method m : clazz.getDeclaredMethods()) { // 所有聲明,不含繼承
    // ...
}

四、動態操作字段

1. 獲取字段


Field f = clazz.getDeclaredField("fieldName");
f.setAccessible(true);
f.set(obj, "newValue"); // 設置值
Object val = f.get(obj); // 獲取值

2. 區別 getFields / getDeclaredFields

  • getFields():只獲取 public 字段,含繼承
  • getDeclaredFields():獲取所有聲明字段,不含繼承

五、泛型擦除與反射獲取泛型信息

Java 泛型擦除導致運行時無法直接獲取泛型參數,但可通過 Type 體系 (ParameterizedType) 獲取字段、方法簽名上的泛型信息。


Field f = clazz.getDeclaredField("list");
Type genericType = f.getGenericType(); // 獲取泛型類型
if (genericType instanceof ParameterizedType) {
    ParameterizedType pt = (ParameterizedType) genericType;
    System.out.println("原始類型: " + pt.getRawType()); // interface List
    System.out.println("實際類型參數: " + Arrays.toString(pt.getActualTypeArguments())); // [class String]
}

六、動態代理

1. JDK 動態代理 (基於接口)


interface Subject { String say(String name, int age); }
class RealSubject implements Subject { public String say(String n, int a) { return n + " " + a; } }

class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before: " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("After: " + method.getName());
        return result;
    }
}

// 使用
Subject proxy = (Subject) new MyInvocationHandler().bind(new RealSubject());
proxy.say("Rollen", 20); // 輸出 Before/After 並返回結果

2. CGLIB 代理 (基於繼承,無需接口) - 需引入依賴


Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MethodInterceptor() {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After");
        return result;
    }
});
RealSubject proxy = (RealSubject) enhancer.create();

七、類加載器與類生命週期

1. 三大類加載器

  • Bootstrap ClassLoader:C++ 實現,加載 JAVA_HOME/jre/lib/rt.jar 核心庫
  • Extension ClassLoader:加載 jre/lib/ext 擴展庫
  • AppClassLoader (System):加載 classpath 應用類,默認加載器

System.out.println(String.class.getClassLoader()); // null (Bootstrap)
System.out.println(MyClass.class.getClassLoader()); // sun.misc.Launcher$AppClassLoader

2. 類生命週期:加載 → 驗證 → 準備 → 解析 → 初始化 → 使用 → 卸載

  • 加載:查找字節碼,生成 Class 對象
  • 鏈接:驗證格式、準備靜態字段賦默認值、解析符號引用
  • 初始化:執行 <clinit>() (靜態塊、靜態字段賦值)
  • 卸載:Class 對象無引用,GC 回收 (需自定義類加載器)

八、反射操作數組


int[] arr = {1,2,3,4,5};
Class compType = arr.getClass().getComponentType(); // int.class
int len = Array.getLength(arr); // 5
Object first = Array.get(arr, 0); // 1
Array.set(arr, 0, 100); // 修改第一個元素為 100

// 動態擴容數組
int[] newArr = (int[]) Array.newInstance(compType, 15);
System.arraycopy(arr, 0, newArr, 0, arr.length);
// newArr: [1,2,3,4,5,0,0,0,0,0,0,0,0,0,0]

九、反射在工廠模式中的應用:從硬編碼到配置驅動

1. 傳統工廠 (違反開閉原則)


class Factory { public static Fruit getInstance(String name) {
    if ("Apple".equals(name)) return new Apple();
    if ("Orange".equals(name)) return new Orange();
    return null;
}}

2. 反射工廠 (新增產品無需修改工廠)


class Factory { public static Fruit getInstance(String className) {
    try { return (Fruit) Class.forName(className).newInstance(); }
    catch (Exception e) { throw new RuntimeException(e); }
}}
// 調用: Factory.getInstance("pkg.Apple");

3. 配置文件驅動 (屬性文件/JSON/XML)


// fruit.properties
apple=pkg.Apple
orange=pkg.Orange

Properties props = new Properties();
props.load(new FileInputStream("fruit.properties"));
Fruit fruit = Factory.getInstance(props.getProperty("apple"));

十、性能與安全考量

方面影響優化建議
性能反射調用比直接調用慢 10-100 倍 (方法訪問檢查、參數裝箱)緩存 Class/Method/Field/Constructor 對象;熱點路徑用 MethodHandle/VarHandle (Java 9+)
安全破壞封裝 (setAccessible)、任意代碼執行風險SecurityManager 限制;模塊化 (Java 9+) 導出控制;避免反序列化不可信數據
可讀性代碼晦澀、編譯期無檢查封裝為工具類;優先用接口/繼承/註解處理器替代

十一、最佳實踐總結

  • 反射是框架級工具 (Spring、Hibernate、MyBatis、Jackson 等大量使用),業務代碼盡量少用。
  • 緩存反射對象:Class、Method、Field、Constructor 線程安全,可靜態緩存。
  • 優先用 getDeclaredConstructor/Method/Field + setAccessible(true) 而非 getConstructor/Method/Field,支持私有成員。
  • 操作泛型集合時,利用 TypeToken (Gson) 或 ParameterizedType 保留泛型信息。
  • 動態代理首選 JDK Proxy (接口) 或 ByteBuddy (現代、無依賴、功能強),避免直接用 CGLIB。
  • Java 9+ 模塊化:需在 module-info.java 中 opens pkg to reflection.framework 才能深度反射。

反射賦予 Java 動態靈魂,善用之則框架優雅,濫用之則代碼脆弱。理解本質,謹慎應用。

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