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

Java 反射機制核心 API 深度解析:Class、Constructor、Field、Method 完整實戰

     編輯:Java 綜合教學

Java 反射完全指南:從 Class 對象獲取到動態創建實例、訪問字段、調用方法的全流程實戰

反射是 Java 動態性的基石,框架開發 (Spring、Hibernate、MyBatis) 的核心技術。本文以 Person 實體為例,系統講解 Class、Constructor、Field、Method 四大核心 API 的獲取與使用姿勢。

實體類定義 (測試用例)

package com.jalja.org.base.reflect;

public class Person {
    private String name;
    int age;                          // package-private
    public String address;            // public
    
    public Person() {}                // public 無參
    private Person(String name) {     // private 有參
        this.name = name;
    }
    Person(String name, int age) {    // package-private 有參
        this.name = name;
        this.age = age;
    }
    public Person(String name, int age, String address) { // public 全參
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
    public void show() { System.out.println("show"); }
    public void method(String s) { System.out.println("method " + s); }
    public String getString(String s, int i) { return s + "---" + i; }
    private void function() { System.out.println("function"); }
    
    @Override public String toString() {
        return "Person[name=" + name + ", age=" + age + ", address=" + address + "]";
    }
}

一、獲取 Class 對象的三種方式

public static void getClassObject() throws ClassNotFoundException {
    // 方式 1:實例.getClass() - 最常用
    Person p1 = new Person();
    Person p2 = new Person();
    Class c1 = p1.getClass();
    Class c2 = p2.getClass();
    System.out.println("p1==p2: " + (p1 == p2));  // false (不同實例)
    System.out.println("c1==c2: " + (c1 == c2));  // true  (同一 Class 對象)
    
    // 方式 2:類字面常量 - 編譯期已知類型時首選
    Class c3 = Person.class;
    System.out.println("c1==c3: " + (c1 == c3));  // true
    
    // 方式 3:Class.forName() - 動態載入,配置驅動開發必備
    Class c4 = Class.forName("com.jalja.org.base.reflect.Person");
    System.out.println("c1==c4: " + (c1 == c4));  // true
    
    // 核心結論:JVM 中每個類型僅有一個 Class 對象 (同一類加載器下)
}

二、Constructor:動態創建實例

1. 獲取 Constructor 對象

public static void getConstructorTest() throws Exception {
    Class clazz = Class.forName("com.jalja.org.base.reflect.Person");
    
    // 所有 public 構造器
    Constructor[] publicCtors = clazz.getConstructors();
    // 所有聲明構造器 (含 private/protected/package),不含繼承
    Constructor[] declaredCtors = clazz.getDeclaredConstructors();
    
    // 獲取特定 public 構造器:無參
    Constructor ctorNoArg = clazz.getConstructor();  // 等同 getConstructor(new Class[0])
    
    // 獲取特定 public 構造器:全參 (String, int, String)
    Constructor ctorFull = clazz.getConstructor(String.class, int.class, String.class);
    
    // 獲取特定非 public 構造器:private Person(String)
    Constructor ctorPrivate = clazz.getDeclaredConstructor(String.class);
}

2. 通過 Constructor 創建實例

public static void createObject() throws Exception {
    Class clazz = Class.forName("com.jalja.org.base.reflect.Person");
    
    // 1. 調用 public 無參構造器
    Constructor ctorNoArg = clazz.getConstructor();
    Object obj1 = ctorNoArg.newInstance();  // 相當於 new Person()
    System.out.println(obj1);  // Person[name=null, age=0, address=null]
    
    // 2. 調用 public 全參構造器
    Constructor ctorFull = clazz.getConstructor(String.class, int.class, String.class);
    Object obj2 = ctorFull.newInstance("Jalja", 21, "北京");
    System.out.println(obj2);  // Person[name=Jalja, age=21, address=北京]
    
    // 3. 調用 private 構造器 - 必須破壞封裝
    Constructor ctorPrivate = clazz.getDeclaredConstructor(String.class);
    ctorPrivate.setAccessible(true);  // 關鍵:取消 Java 訪問檢查
    Object obj3 = ctorPrivate.newInstance("張三豐");
    System.out.println(obj3);  // Person[name=張三豐, age=0, address=null]
}

三、Field:動態訪問/修改字段

1. 獲取 Field 對象

public static void getFieldTest() throws Exception {
    Class clazz = Class.forName("com.jalja.org.base.reflect.Person");
    
    // 所有 public 字段 (含繼承)
    Field[] publicFields = clazz.getFields();
    
    // 所有聲明字段 (含 private/protected/package),不含繼承
    Field[] declaredFields = clazz.getDeclaredFields();
    
    // 獲取特定 public 字段
    Field addressField = clazz.getField("address");
    
    // 獲取特定非 public 字段
    Field nameField = clazz.getDeclaredField("name");
}

2. 字段讀寫操作

public static void fieldReadWrite() throws Exception {
    Class clazz = Class.forName("com.jalja.org.base.reflect.Person");
    Object obj = clazz.getConstructor().newInstance();  // 新實例
    
    // 1. 修改 public 字段
    Field addressField = clazz.getField("address");
    addressField.set(obj, "上海");  // set(Object target, Object value)
    System.out.println(obj);  // Person[name=null, age=0, address=上海]
    
    // 2. 修改 private 字段 - 需 setAccessible
    Field nameField = clazz.getDeclaredField("name");
    nameField.setAccessible(true);
    nameField.set(obj, "李四");
    System.out.println(obj);  // Person[name=李四, age=0, address=上海]
    
    // 3. 讀取字段值
    Object nameValue = nameField.get(obj);
    System.out.println("name: " + nameValue);  // 李四
    
    // 基本類型自動拆箱/裝箱
    Field ageField = clazz.getDeclaredField("age");
    ageField.setAccessible(true);
    ageField.set(obj, 25);  // 自動裝箱 int -> Integer
    int age = ageField.getInt(obj);  // 專用 getInt 避免裝箱開銷
    System.out.println("age: " + age);  // 25
}

四、Method:動態調用方法

1. 獲取 Method 對象

public static void getMethodTest() throws Exception {
    Class clazz = Class.forName("com.jalja.org.base.reflect.Person");
    
    // 所有 public 方法 (含繼承自 Object)
    Method[] publicMethods = clazz.getMethods();
    
    // 所有聲明方法 (含 private),不含繼承
    Method[] declaredMethods = clazz.getDeclaredMethods();
    
    // 獲取特定 public 方法:無參
    Method showMethod = clazz.getMethod("show");
    
    // 獲取特定 public 方法:有參 (String)
    Method methodMethod = clazz.getMethod("method", String.class);
    
    // 獲取特定 public 方法:有參且有返回值 (String, int)
    Method getStringMethod = clazz.getMethod("getString", String.class, int.class);
    
    // 獲取特定非 public 方法:private 無參
    Method functionMethod = clazz.getDeclaredMethod("function");
}

2. 方法調用

public static void methodInvoke() throws Exception {
    Class clazz = Class.forName("com.jalja.org.base.reflect.Person");
    Object obj = clazz.getConstructor().newInstance();
    
    // 1. 調用無參方法
    Method showMethod = clazz.getMethod("show");
    showMethod.invoke(obj);  // invoke(Object target, Object... args)
    
    // 2. 調用有參方法
    Method methodMethod = clazz.getMethod("method", String.class);
    methodMethod.invoke(obj, "北京");  // 參數順序對應方法簽名
    
    // 3. 調用有返回值方法
    Method getStringMethod = clazz.getMethod("getString", String.class, int.class);
    Object result = getStringMethod.invoke(obj, "北京", 200);
    System.out.println("返回值: " + result);  // 北京---200
    
    // 4. 調用 private 方法
    Method functionMethod = clazz.getDeclaredMethod("function");
    functionMethod.setAccessible(true);
    functionMethod.invoke(obj);  // 輸出: function
    
    // 靜態方法調用:target 傳 null
    // Method staticMethod = clazz.getMethod("staticMethod");
    // staticMethod.invoke(null, args);
}

五、反射效能優化與最佳實踐

效能對比 (相對直接調用基準)

操作相對開銷優化策略
Class.forName()高 (類加載、初始化)緩存 Class 對象,避免重複載入
getDeclaredMethod/Field中 (元數據查找)緩存 Member 對象 (Method/Field/Constructor)
setAccessible(true)低 (一次性)初始化時一次性設置,複用 Member
invoke()/newInstance()高 (訪問檢查、參數裝箱、異常包裝)使用 MethodHandle (JDK 7+)、Lambda 元工廠、代碼生成 (ASM/ByteBuddy)
基本類型 get/set中 (裝箱/拆箱)使用 getInt()/setInt() 等專用方法

最佳實踐棧單

  1. 緩存 Class/Member 對象:靜態 Map 或 ConcurrentHashMap 緩存,避免重複查找
  2. 優先使用 MethodHandle:MethodHandles.lookup().unreflect(method) 直接調用,繞過訪問檢查,效能接近直接調用
  3. 避免熱路徑反射:高頻調用路徑 (序列化、ORM、JSON) 必須用代碼生成或 MethodHandle
  4. 異常處理:捕獲 InvocationTargetException 獲取真實業務異常 e.getCause()
  5. 模組化封裝:JDK 9+ 模塊系統需 opens 導出包才能反射私有成員

六、實戰應用場景

  • 依賴注入容器:Spring 掃描 @Component、構造器注入、字段注入 (@Autowired)
  • ORM 框架:Hibernate/MyBatis 實體映射、ResultSet 映射、動態 SQL
  • 序列化庫:Jackson/Gson/fastjson 字段遍歷、多態反序列化
  • 動態代理:JDK Proxy / CGLIB / ByteBuddy 實現 AOP、RPC 框架
  • 配置綁定:Spring @ConfigurationProperties、YAML/Properties 映射到 Bean
  • 測試框架:JUnit/TestNG 參數化測試、Mockito 模擬、PowerMock 靜態模擬
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved