Java 反射 是框架開發 (Spring、Hibernate、MyBatis、Jackson) 的基石,允許運行時檢查類結構、動態創建對象、訪問/修改私有成員、調用任意方法。本文通過完整的 `Book`/`Page` 模型,演示反射核心 API 的實戰用法:字段操作、泛型類型擷取、構造器/方法調用、繞過訪問控制。
import java.util.ArrayList;
import java.util.List;
public class Book {
public String title;
public int length = 0;
public List pages = null;
@Override public String toString() {
StringBuilder sb = new StringBuilder("Book:\n");
sb.append(" title=").append(title).append("\n");
sb.append(" length=").append(length).append("\n");
sb.append(" pages=").append(pages).append("\n");
if (pages != null) {
for (Page page : pages) sb.append(" ").append(page).append("\n");
}
return sb.toString();
}
}
public class Page {
@Override public String toString() { return "Page@" + Integer.toHexString(hashCode()); }
}
Class> clazz1 = Book.class; // 1. 類字面量 (編譯期已知,最快)
Class> clazz2 = new Book().getClass(); // 2. 實例 getClass() (運行期)
Class> clazz3 = Class.forName("pkg.Book"); // 3. 字符串全限定名 (動態加載,可拋 ClassNotFoundException)
Book book = new Book();
Class> clazz = book.getClass();
// 獲取單個公有字段 (包含繼承)
Field titleField = clazz.getField("title");
titleField.set(book, "反射實戰指南"); // 等同 book.title = "..."
// 獲取所有公有字段 (含父類)
Field[] publicFields = clazz.getFields();
// 批量修改:根據類型設置默認值
for (Field f : publicFields) {
if (f.getType() == String.class) f.set(book, "默認標題");
else if (f.getType() == int.class) f.set(book, 999);
}
// 輸出: title=默認標題, length=999
// 假設 length 改為 private
// private int length = 0;
Field lengthField = clazz.getDeclaredField("length"); // getDeclaredField 可獲取私有字段,不含繼承
lengthField.setAccessible(true); // 關鍵:暴力反射,關閉訪問檢查
lengthField.setInt(book, 888); // 基本類型用 setXxx 避免裝箱
System.out.println(book.length); // 888
注意:JDK 9+ 模組化系統 (`--add-opens`) 可能限制 `setAccessible(true)`。啟動參數添加 `--add-opens java.base/java.lang=ALL-UNNAMED` 可解除限制。
場景:`List
Field pagesField = clazz.getDeclaredField("pages");
pagesField.setAccessible(true);
// 1. 獲取泛型類型信息:List -> ParameterizedType
java.lang.reflect.Type genericType = pagesField.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) genericType;
// 獲取實際類型參數 [0] -> Page.class
Type[] actualTypeArgs = pt.getActualTypeArguments();
Type pageType = actualTypeArgs[0]; // 這裡是 Class
// 2. 動態加載並實例化
Class> pageClass = Class.forName(pageType.getTypeName()); // "pkg.Page"
Object pageInstance = pageClass.getDeclaredConstructor().newInstance(); // 調用無參構造器
// 3. 創建列表並設置
List
// 動態創建實例 (帶參構造器)
Constructor> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Book another = (Book) constructor.newInstance("動態構造", 123);
// 動態調用方法 (含私有)
Method toStringMethod = clazz.getDeclaredMethod("toString");
toStringMethod.setAccessible(true);
String result = (String) toStringMethod.invoke(another); // 等同 another.toString()
// 靜態方法調用 (第一參數傳 null)
Method staticMethod = clazz.getDeclaredMethod("staticHelper", String.class);
staticMethod.invoke(null, "參數");
| 場景 | 優化策略 | 代碼示例 |
|---|---|---|
| 頻繁訪問同一字段/方法 | 緩存 Field/Method/Constructor 對象 | `private static final Field FIELD = clazz.getDeclaredField("x");` 靜態初始化一次 |
| 頻繁創建實例 | 使用構造器對象 `newInstance`,避免 `Class.newInstance()` (已棄用) | `constructor.newInstance(args)` |
| 基本類型設置/獲取 | 用 `setInt`/`getInt` 等專用方法,避免裝箱拆箱 | `field.setInt(obj, 100)` vs `field.set(obj, 100)` |
| 高性能動態調用 | MethodHandle (JDK 7+) / VarHandle (JDK 9+) | `MethodHandles.lookup().findSetter(clazz, "x", int.class).invokeExact(obj, 100)` |
| 框架開發 | 定義註解 + 編譯期處理 (Annotation Processor) 生成代碼,運行期零反射 | MapStruct, Lombok, Room Database 原理 |
反射賦予了 Java 動態性,是框架「約定優於配置」、「依賴注入」、「ORM 映射」的基石。掌握 Field、Method、Constructor、ParameterizedType 等核心 API,配合 `setAccessible(true)` 突破封裝、泛型擷取實現動態實例化,是高階 Java 開發者必備技能。但請記住:**反射打破編譯期檢查、降低效能、增加維護成本、受模組化限制**。業務邏輯首選強類型直接調用;框架/工具開發時,善用緩存、MethodHandle、編譯期代碼生成來平衡靈活性與效能。