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

Java 反射機制全解析:Class、Method、Field 與泛型擦除實戰

     編輯:Java 相關知識

前言:反射——Java 動態性的基石

反射是 Java 語言區別於 C/C++ 等靜態語言的關鍵特性之一,它賦予了程序在運行時檢查、修改類結構與行為的能力。從框架開發(Spring、Hibernate)、動態代理、註解處理,到熱部署、插件化架構,反射無處不在。本文系統梳理 Class 對象獲取、成員訪問、方法調用、構造器實例化,並深入剖析泛型擦除與反射繞過編譯檢查的機制。

Class 對象的三種獲取途徑

每個類在 JVM 中只有一個 Class 實例,代表該類的「類類型」。獲取方式有三種,本質相同:

// 1. 類字面量(編譯期已知,最常用、性能最好)
Class<User> c1 = User.class;

// 2. 實例 getClass()(運行期對象已存在)
User user = new User();
Class<? extends User> c2 = user.getClass();

// 3. 字串全限定名動態加載(框架核心,支援熱插拔)
Class<?> c3 = Class.forName("com.wx.model.User");

// 驗證單例性
System.out.println(c1 == c2); // true
System.out.println(c2 == c3); // true

關鍵點:Class.forName() 不僅返回類類型,更會觸發類的初始化(執行靜態塊、靜態字段賦值),這是靜態加載與動態加載的分水嶺。

基本類型與 void 也有 Class 對象

Class<Integer> cInt = int.class;      // 基本類型
Class<Double> cDouble = double.class;
Class<Void> cVoid = void.class;        // void 關鍵字
Class<String> cStr = String.class;    // 引用類型

System.out.println(cInt.getName());        // "int"
System.out.println(cStr.getSimpleName());  // "String" (不含包名)

類結構的完整反射掃描

掃描所有 public 方法(含繼承)


public static void printClassMethods(Object obj) {
    Class<?> clazz = obj.getClass();
    System.out.println("類名:" + clazz.getName());
    
    // getMethods() 獲取所有 public 方法(含父類)
    Method[] methods = clazz.getMethods();
    for (Method m : methods) {
        // 返回類型
        Class<?> returnType = m.getReturnType();
        System.out.print(returnType.getName() + " ");
        
        // 方法名
        System.out.print(m.getName() + "(");
        
        // 參數列表
        Class<?>[] paramTypes = m.getParameterTypes();
        for (int i = 0; i < paramTypes.length; i++) {
            System.out.print(paramTypes[i].getName());
            if (i < paramTypes.length - 1) System.out.print(", ");
        }
        System.out.println(")");
    }
}

掃描類自行聲明的所有成員(不問訪問權限)


// 字段
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
    System.out.println(f.getType().getName() + " " + f.getName());
}

// 構造器
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> c : constructors) {
    System.out.print(c.getName() + "(");
    Class<?>[] params = c.getParameterTypes();
    for (int i = 0; i < params.length; i++) {
        System.out.print(params[i].getName());
        if (i < params.length - 1) System.out.print(", ");
    }
    System.out.println(")");
}

方法反射調用:核心 API 與實戰


class A {
    public void print() { System.out.println("helloworld"); }
    public void print(int a, int b) { System.out.println(a + b); }
    public void print(String a, String b) { 
        System.out.println(a.toUpperCase() + "," + b.toLowerCase()); 
    }
}

public class MethodDemo {
    public static void main(String[] args) throws Exception {
        A a1 = new A();
        Class<?> c = a1.getClass();
        
        // 1. 獲取方法:方法名 + 參數類型數組/可變參數
        Method m1 = c.getMethod("print", int.class, int.class);
        Method m2 = c.getMethod("print", String.class, String.class);
        Method m3 = c.getMethod("print"); // 無參
        
        // 2. 調用:invoke(實例, 參數...)
        // 無返回值返回 null,有返回值返回具體值
        m1.invoke(a1, 10, 20);           // 輸出 30
        m2.invoke(a1, "hello", "WORLD"); // 輸出 HELLO,world
        m3.invoke(a1);                   // 輸出 helloworld
    }
}

注意:getMethod 僅查找 public 方法;私有方法需用 getDeclaredMethod 並調用 setAccessible(true)。

泛型擦除與反射繞過編譯檢查

Java 泛型採用擦除機制:編譯後泛型信息被擦除,運行期 List<String> 和 List<Integer> 共用同一個 Class 對象。


List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();

System.out.println(list1.getClass() == list2.getClass()); // true

// 反射繞過編譯器泛型檢查
Method addMethod = list1.getClass().getMethod("add", Object.class);
addMethod.invoke(list1, 123); // 成功將 Integer 放入 List!
System.out.println(list1); // [123]

這證明了:泛型僅在編譯期生效,運行期可通過反射繞過類型約束。這也是為什麼集合框架內部大量使用 Object[] 存儲元素的原因。

實戰工具:屬性值動態獲取


/**
 * 根據標準 JavaBean 屬性名獲取屬性值
 * @param obj 目標對象
 * @param propertyName 屬性名(如 "userName")
 * @return 屬性值
 */
public static Object getPropertyValue(Object obj, String propertyName) 
        throws ReflectiveOperationException {
    // 1. 拼接 getter 方法名:get + 首字母大寫 + 其餘
    String getterName = "get" + propertyName.substring(0, 1).toUpperCase() 
                        + propertyName.substring(1);
    
    // 2. 獲取 Method 對象(public 無參)
    Method getter = obj.getClass().getMethod(getterName);
    
    // 3. 反射調用
    return getter.invoke(obj);
}

反射性能優化建議

  1. 緩存 Class、Method、Field 對象:反射調用涉及安全檢查、方法查找,開銷較大。框架啟動時應一次性掃描並緩存。
  2. setAccessible(true) 謹慎使用:雖能訪問私有成員,但會跳過 JVM 訪問檢查,可能導致安全風險與 JIT 優化失效。
  3. 優先考慮 MethodHandle / VarHandle (Java 9+):提供更底層、更高效的動態調用機制,適合高性能場景。
  4. 避免熱路徑反射:高頻調用路徑(如循環、消息處理)盡量用直接調用或代碼生成(ASM、ByteBuddy)替代。

總結

反射是 Java 動態能力的核心,掌握 Class、Method、Field、Constructor 四大核心類的協作模式,理解泛型擦除本質,能讓你在框架開發、動態代理、配置綁定等場景遊刃有餘。但「大權在手,責任在肩」——合理控制反射使用範圍,做好緩存與異常處理,才能兼得靈活與性能。

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