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

Java 反射機制實戰:從基礎 API 到動態修改私有字段、泛型解析、實例創建

     編輯:Java 相關知識

前言

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()); }
}

反射核心實戰

1. 獲取 Class 對象的三種方式

Class clazz1 = Book.class;                    // 1. 類字面量 (編譯期已知,最快)
Class clazz2 = new Book().getClass();         // 2. 實例 getClass() (運行期)
Class clazz3 = Class.forName("pkg.Book");     // 3. 字符串全限定名 (動態加載,可拋 ClassNotFoundException)

2. 訪問與修改公有字段

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

3. 破解私有字段訪問控制 (核心技巧)

// 假設 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` 可解除限制。

4. 泛型類型擷取與動態實例化 (進階)

場景:`List pages`,運行時獲取泛型參數 `Page` 並創建實例加入列表。

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 newPages = new ArrayList<>();
    newPages.add(pageInstance);
    pagesField.set(book, newPages);
}
System.out.println(book); // pages 包含一個 Page 實例

5. 動態調用構造器與方法

// 動態創建實例 (帶參構造器)
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 原理

安全性與模組化考量 (JDK 9+)

  • 模組封裝:`java.base` 等模組默認不導出內部包,反射訪問非導出包的私有成員會拋 `InaccessibleObjectException`。
  • 解決方案:
    • 命令行:`--add-opens java.base/java.lang=ALL-UNNAMED`
    • 代碼中 (僅早期啟動階段):`Module.class.getDeclaredMethod("addOpens", String.class, Module.class).invoke(targetModule, "package", ModuleLayer.boot().findModule("unnamed").orElseThrow())`
  • 原則:業務代碼**盡量避免**破壞封裝;框架代碼需文檔說明啟動參數要求。

總結

反射賦予了 Java 動態性,是框架「約定優於配置」、「依賴注入」、「ORM 映射」的基石。掌握 Field、Method、Constructor、ParameterizedType 等核心 API,配合 `setAccessible(true)` 突破封裝、泛型擷取實現動態實例化,是高階 Java 開發者必備技能。但請記住:**反射打破編譯期檢查、降低效能、增加維護成本、受模組化限制**。業務邏輯首選強類型直接調用;框架/工具開發時,善用緩存、MethodHandle、編譯期代碼生成來平衡靈活性與效能。

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