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

《編寫高質量代碼》第 7 章精讀:泛型與反射最佳實踐 (建議 102-105)

     編輯:Java 綜合教學

前言

《編寫高質量代碼:改善 Java 程序的 151 個建議》第 7 章聚焦泛型與反射。本文深度解析建議 102-105,結合源碼、實戰場景、性能數據,幫助開發者掌握反射 API 正確姿勢、動態加載機制、數組反射特殊處理,避開常見坑點。

建議 102:適時選擇 getDeclaredXXX 與 getXXX

核心差異

方法組訪問範圍權限檢查繼承鏈
getDeclaredMethod/Field/Constructor僅當前類聲明的成員不受 public/private 限制不包含父類繼承成員
getMethod/Field/Constructor當前類 + 父類鏈的 public 成員受權限檢查包含繼承的 public 成員

實戰驗證

public class Client102 {
    public static void main(String[] args) throws Exception {
        String methodName = "doStuff";
        
        // 獲取私有方法:只能用 getDeclaredMethod
        Method m1 = Foo.class.getDeclaredMethod(methodName);
        
        // 獲取公有方法:getMethod 可行
        // Method m2 = Foo.class.getMethod(methodName); // 拋 NoSuchMethodException!
    }
    
    static class Foo {
        private void doStuff() {} // 私有方法
    }
}

運行結果:getMethod 拋出 NoSuchMethodException,因它只能看到 public 方法。

列出所有繼承方法技巧

public static List getAllMethods(Class clazz) {
    List methods = new ArrayList<>();
    while (clazz != null) {
        Collections.addAll(methods, clazz.getDeclaredMethods());
        clazz = clazz.getSuperclass();
    }
    return methods;
}

建議 103:反射訪問成員時必須設置 setAccessible(true)

Accessible 真實含義

AccessibleObject (Field/Method/Constructor 父類) 中的 override 字段不代表語法層面的訪問權限,而是是否跳過安全檢查:

  • true:跳過 Java 安全管理器檢查,直接執行,性能提升 ~20 倍,且可訪問 private 成員
  • false (默認):執行完整安全檢查

源碼證據

// Method.invoke 核心邏輯
public Object invoke(Object obj, Object... args) throws ... {
    if (!override) { // override = isAccessible()
        // 執行安全檢查:Reflection.quickCheckMemberAccess + checkAccess
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class caller = Reflection.getCallerClass(1);
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    // 直接執行
    MethodAccessor ma = methodAccessor;
    return ma.invoke(obj, args);
}

標準習慣用法

Method method = clazz.getDeclaredMethod("methodName");
if (!method.isAccessible()) {
    method.setAccessible(true); // 必須在 invoke 前設置
}
method.invoke(target, args);

延伸

  • Field 設置值、Constructor 實例化同理必須 setAccessible(true)
  • 框架層 (Spring/MyBatis/Hibernate) 大量使用此模式實現高性能反射

建議 104:使用 Class.forName 動態加載類文件

動態加載本質

Class.forName(String className):僅將類加載到 JVM 內存,觸發類初始化(執行 static 塊、初始化 static 字段),不創建實例對象。

核心價值:靜態代碼塊自動註冊模式

// 數據庫驅動註冊經典案例
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            DriverManager.registerDriver(new Driver()); // 自動註冊
        } catch (SQLException e) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

// 應用代碼
Class.forName("com.mysql.jdbc.Driver"); // 執行 static 塊,完成註冊
Connection conn = DriverManager.getConnection(url); // 直接獲取連接

關鍵認知

  1. forName 只負責加載類、執行 static 初始化,不執行任何實例方法
  2. 若類無 static 邏輯,forName 僅加載元數據,無副作用
  3. 動態加載核心場景:插件系統、SPI 機制、驅動註冊、配置驅動的框架初始化

建議 105:動態加載不適合數組,請使用 Array 工具類

數組類型的特殊編譯表示

Java 數組是特殊類,編譯後類型簽名與源碼聲明完全不同:

源碼類型編譯後類型簽名
byte[][B
char[][C
double[][D
float[][F
int[][I
long[][J
short[][S
boolean[][Z
String[] (引用類型)[Ljava.lang.String;

forName 加載數組類的局限

// 可行:加載數組類元數據
Class.forName("[Ljava.lang.String;"); // String[] 類
Class.forName("[J");                   // long[] 類

// 不可行:創建數組實例
// Class.forName("[Ljava.lang.String;").newInstance(); // 拋 InstantiationException:數組無無參構造器
// 無法指定數組長度!

正確方案:java.lang.reflect.Array

// 動態創建一維數組
String[] strs = (String[]) Array.newInstance(String.class, 8);

// 動態創建多維數組
int[][] matrix = (int[][]) Array.newInstance(int.class, 2, 3);

// 動態訪問/設置數組元素
Array.set(strs, 0, "hello");
String first = (String) Array.get(strs, 0);

核心原則

操作數組反射,請使用 java.lang.reflect.Array,不要使用通用反射 API (Class/Constructor/Field/Method)。

總結與心得

建議核心要點實戰價值
102getDeclaredXXX 看本類(含私有),getXXX 看繼承鏈 public精確控制反射掃描範圍,避免誤用
103setAccessible(true) 跳過安全檢查,性能提升 20 倍+框架開發必備,業務代碼慎用
104forName 加載類執行 static 初始化,SPI/驅動註冊核心機制理解框架啟動原理,設計插件架構
105數組反射用 Array 類,forName 無法創建數組實例避開數組反射大坑,正確動態創建數組

反射是把雙刃劍:賦予極大靈活性的同時帶來性能損耗、安全風險、可讀性下降。業務代碼盡量避免反射,框架代碼精準使用反射,是架構分層的重要原則。

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