《編寫高質量代碼:改善 Java 程序的 151 個建議》第 7 章聚焦泛型與反射。本文深度解析建議 102-105,結合源碼、實戰場景、性能數據,幫助開發者掌握反射 API 正確姿勢、動態加載機制、數組反射特殊處理,避開常見坑點。
| 方法組 | 訪問範圍 | 權限檢查 | 繼承鏈 |
|---|---|---|---|
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 ListgetAllMethods(Class> clazz) { List methods = new ArrayList<>(); while (clazz != null) { Collections.addAll(methods, clazz.getDeclaredMethods()); clazz = clazz.getSuperclass(); } return methods; }
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)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); // 直接獲取連接
forName 只負責加載類、執行 static 初始化,不執行任何實例方法static 邏輯,forName 僅加載元數據,無副作用Java 數組是特殊類,編譯後類型簽名與源碼聲明完全不同:
| 源碼類型 | 編譯後類型簽名 |
|---|---|
byte[] | [B |
char[] | [C |
double[] | [D |
float[] | [F |
int[] | [I |
long[] | [J |
short[] | [S |
boolean[] | [Z |
String[] (引用類型) | [Ljava.lang.String; |
// 可行:加載數組類元數據
Class.forName("[Ljava.lang.String;"); // String[] 類
Class.forName("[J"); // long[] 類
// 不可行:創建數組實例
// Class.forName("[Ljava.lang.String;").newInstance(); // 拋 InstantiationException:數組無無參構造器
// 無法指定數組長度!
// 動態創建一維數組 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)。
| 建議 | 核心要點 | 實戰價值 |
|---|---|---|
| 102 | getDeclaredXXX 看本類(含私有),getXXX 看繼承鏈 public | 精確控制反射掃描範圍,避免誤用 |
| 103 | setAccessible(true) 跳過安全檢查,性能提升 20 倍+ | 框架開發必備,業務代碼慎用 |
| 104 | forName 加載類執行 static 初始化,SPI/驅動註冊核心機制 | 理解框架啟動原理,設計插件架構 |
| 105 | 數組反射用 Array 類,forName 無法創建數組實例 | 避開數組反射大坑,正確動態創建數組 |
反射是把雙刃劍:賦予極大靈活性的同時帶來性能損耗、安全風險、可讀性下降。業務代碼盡量避免反射,框架代碼精準使用反射,是架構分層的重要原則。