反射是 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 + "]";
}
}
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 對象 (同一類加載器下)
}
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);
}
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]
}
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");
}
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
}
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");
}
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() 等專用方法 |
MethodHandles.lookup().unreflect(method) 直接調用,繞過訪問檢查,效能接近直接調用InvocationTargetException 獲取真實業務異常 e.getCause()opens 導出包才能反射私有成員@Component、構造器注入、字段注入 (@Autowired)@ConfigurationProperties、YAML/Properties 映射到 Bean