在 Java 編程中,“反射” 是一個貫穿基礎與進階的核心概念,它允許程序在運行時動態獲取類的結構、調用方法、操作屬性,甚至創建對象 —— 無需在編譯期明確知道類的具體信息。
一、反射是什么?
首先明確一個關鍵定義:Java 反射(Reflection)是 Java 語言提供的一種能力,允許程序在運行時(而非編譯時)訪問、檢測和修改類、對象、方法、屬性等程序元素的信息。
反射的核心價值
- 動態性:打破編譯期的固定依賴,運行時靈活操作類結構(比如根據配置文件加載不同類)。
- 通用性:可編寫通用框架(如 Spring、MyBatis),通過反射適配任意類,無需為每個類單獨寫代碼。
- 穿透性:支持 “暴力反射”,可突破類成員的訪問權限限制(如操作 private 私有屬性 / 方法)。
二、反射的前提:獲取 “類對象”
反射的所有操作,都必須基于一個核心載體 ——Class 類的對象(簡稱 “類對象”)。每個類在 JVM 中只會被加載一次,因此同一個類的類對象全局唯一。
獲取類對象的 3 種核心方式
這 3 種方式對應類的 “生命周期”(硬盤→內存→對象)整理如下:
獲取方式 | 語法示例 | 適用場景 | 關鍵說明 |
---|---|---|---|
1. Class.forName (全類名) | Class pClass = Class.forName("reflect.Person"); | 編譯期未知類名,需動態指定(如讀配置文件) | 需傳入 “包名 + 類名”,會觸發類的加載,可能拋出?ClassNotFoundException |
2. 類名.class | Class pClass = Person.class; | 編譯期已知類名,需靜態獲取 | 不會觸發類的加載,僅獲取類的靜態結構信息 |
3. 對象.getClass () | Person person = new Person(); Class pClass = person.getClass(); | 已有對象實例,需通過對象反推類信息 | 依賴具體對象,適用于 “已知對象但未知類” 的場景 |
驗證唯一性:同一個類的 3 種方式獲取的類對象地址完全相同
Class class1 = Class.forName("reflect.Person");
Class class2 = Person.class;
Person person = new Person();
Class class3 = person.getClass();// 輸出結果均為 true,證明類對象唯一
System.out.println(class1 == class2);
System.out.println(class2 == class3);
三、反射核心操作:全方位操控類結構
獲取類對象后,即可通過反射 API 操作類的三大核心組件:成員變量、成員方法、構造方法。以下結合 Test.java 和 Person.java 的實踐代碼,分模塊梳理。
模塊 1:操作 “成員變量”(Field)
成員變量的反射操作,核心是 “獲取變量” 和 “讀寫變量值”,需區分 “所有權限” 和 “僅公共權限(public)”,同時支持暴力反射突破私有限制。
1.1 獲取成員變量的 4 個核心方法
方法名 | 作用 | 訪問權限范圍 | 是否包含父類變量 |
---|---|---|---|
getDeclaredFields() | 獲取當前類的所有成員變量 | 任意權限(public/private/protected) | 不包含 |
getDeclaredField(String name) | 獲取當前類的指定名稱成員變量 | 任意權限 | 不包含 |
getFields() | 獲取當前類的所有公共成員變量 | 僅 public | 包含父類的 public 變量 |
1.2 讀寫變量值:set () 與 get ()
- 語法:
- 寫值:
field.set(對象實例, 變量值)
(為指定對象的該變量賦值) - 讀值:
field.get(對象實例)
(獲取指定對象的該變量值)
- 寫值:
- 關鍵注意:若變量是?
private
?私有權限,直接讀寫會拋出?IllegalAccessException
,需先調用?field.setAccessible(true)
?開啟 “暴力反射”,強制跳過權限檢查。
1.3 實戰代碼
// 1. 創建 Person 對象實例
Person person = new Person();
// 2. 獲取類對象
Class pClass = Class.forName("reflect.Person");// 3. 獲取所有成員變量(含 private)
Field[] allFields = pClass.getDeclaredFields();
for (Field field : allFields) {System.out.println(field); // 輸出:private java.lang.String reflect.Person.name、private int reflect.Person.age、public java.lang.String reflect.Person.from
}// 4. 獲取指定私有變量 name(需暴力反射)
Field nameField = pClass.getDeclaredField("name");
nameField.setAccessible(true); // 開啟暴力反射,突破 private 限制
nameField.set(person, "趙嘉成"); // 賦值
System.out.println(nameField.get(person)); // 取值,輸出:趙嘉成// 5. 獲取指定公共變量 from(無需暴力反射)
Field fromField = pClass.getDeclaredField("from");
fromField.set(person, "中國"); // 直接賦值
System.out.println(person); // 輸出:Person [name=趙嘉成, age=0, from=中國]
模塊 2:操作 “成員方法”(Method)
成員方法的反射操作,核心是 “獲取方法” 和 “執行方法”,同樣需區分權限范圍,支持暴力反射調用私有方法。
2.1 獲取成員方法的 4 個核心方法
方法名 | 作用 | 訪問權限范圍 | 是否包含父類方法 |
---|---|---|---|
getDeclaredMethods() | 獲取當前類的所有成員方法 | 任意權限 | 不包含 |
getDeclaredMethod(String name, Class<?>... paramTypes) | 獲取當前類的指定名稱和參數列表的方法 | 任意權限 | 不包含 |
getMethods() | 獲取當前類的所有公共成員方法 | 僅 public | 包含父類的 public 方法(如 Object 的 toString ()) |
getMethod(String name, Class<?>... paramTypes) | 獲取當前類的指定名稱和參數列表的公共方法 | 僅 public | 包含父類的 public 方法 |
2.2 執行方法:invoke ()
語法:method.invoke(對象實例, 方法參數值...)
- 若方法是靜態方法(static),對象實例可傳?
null
; - 若方法無參數,參數值部分可省略或傳空數組;
- 若方法有返回值,
invoke()
?會返回該值(需強轉)。
權限注意:私有方法需先調用?method.setAccessible(true)
?開啟暴力反射。
2.3 實戰代碼
// 1. 獲取類對象(省略,同上)
Class pClass = Class.forName("reflect.Person");
Person person = new Person();// 2. 獲取所有方法(含 private 的 run())
Method[] allMethods = pClass.getDeclaredMethods();
for (Method method : allMethods) {System.out.println(method); // 輸出:getAge()、setAge(int)、run() 等
}// 3. 獲取指定私有方法 run()(無參數)
Method runMethod = pClass.getDeclaredMethod("run");
runMethod.setAccessible(true); // 暴力反射突破 private
runMethod.invoke(person); // 執行 run() 方法(無返回值)// 4. 獲取指定公共方法 getAge()(無參數,有返回值)
Method getAgeMethod = pClass.getMethod("getAge");
int age = (int) getAgeMethod.invoke(person); // 執行并接收返回值
System.out.println(age); // 輸出:0(Person 初始 age 為 0)
模塊 3:操作 “構造方法”(Constructor)
構造方法的反射操作,核心是 “獲取構造器” 和 “創建對象”(替代?new
?關鍵字),支持通過無參 / 有參構造器創建實例。
3.1 獲取構造方法的 4 個核心方法
方法名 | 作用 | 訪問權限范圍 | 是否包含父類構造器 |
---|---|---|---|
getDeclaredConstructors() | 獲取當前類的所有構造方法 | 任意權限 | 不包含(構造器不能繼承) |
getDeclaredConstructor(Class<?>... paramTypes) | 獲取當前類的指定參數列表的構造方法 | 任意權限 | 不包含 |
getConstructors() | 獲取當前類的所有公共構造方法 | 僅 public | 不包含 |
getConstructor(Class<?>... paramTypes) | 獲取當前類的指定參數列表的公共構造方法 | 僅 public | 不包含 |
3.2 創建對象:newInstance ()
- 語法:
constructor.newInstance(構造參數值...)
- 無參構造器:參數值部分可省略,直接?
constructor.newInstance()
; - 有參構造器:需傳入與參數列表匹配的參數值;
- 私有構造器:需先調用?
constructor.setAccessible(true)
?開啟暴力反射。
- 無參構造器:參數值部分可省略,直接?
3.3 實戰代碼
// 1. 獲取類對象(省略)
Class pClass = Class.forName("reflect.Person");// 2. 獲取所有構造器(Person 有 3 個:無參、String name、String name+int age)
Constructor[] allConstructors = pClass.getDeclaredConstructors();
for (Constructor constructor : allConstructors) {System.out.println(constructor); // 輸出:Person()、Person(java.lang.String)、Person(java.lang.String,int)
}// 3. 獲取無參公共構造器,創建對象
Constructor noArgConstructor = pClass.getConstructor();
Person person1 = (Person) noArgConstructor.newInstance();
System.out.println(person1); // 輸出:Person [name=null, age=0, from=null]// 4. 獲取有參公共構造器(String name + int age),創建對象
Constructor twoArgConstructor = pClass.getConstructor(String.class, int.class);
Person person2 = (Person) twoArgConstructor.newInstance("麗麗", 20);
System.out.println(person2); // 輸出:Person [name=麗麗, age=20, from=null]
四、反射實戰:結合配置文件實現 “動態加載”
反射的核心優勢是 “動態性”,而結合配置文件(如 .properties)可實現 “不修改代碼,僅改配置就能切換類 / 方法”。Test.java 中已實現該場景,整理如下:
需求場景
通過?refconfig.properties
?配置文件指定類名和方法名,運行時動態加載類并調用方法,無需硬編碼類名。
步驟拆解與代碼
創建配置文件(refconfig.properties):
# 配置要加載的類(全類名)
reflect.className=reflect.Person
# 配置要調用的方法名
reflect.methodName=getAge
讀取配置文件并動態反射:
// 1. 創建 Properties 對象,用于讀取配置文件
Properties props = new Properties();// 2. 通過類加載器加載配置文件(注意路徑:src/main/resources/reflect/refconfig.properties)
InputStream in = Person.class.getClassLoader().getResourceAsStream("reflect/refconfig.properties");
props.load(in); // 讀取配置內容// 3. 從配置中獲取類名和方法名
String className = props.getProperty("reflect.className");
String methodName = props.getProperty("reflect.methodName");// 4. 動態加載類,獲取類對象
Class dynamicClass = Class.forName(className);
System.out.println(dynamicClass); // 輸出:class reflect.Person// 5. 動態獲取方法并執行(這里以無參方法 getAge() 為例)
Method dynamicMethod = dynamicClass.getMethod(methodName);
Object instance = dynamicClass.getConstructor().newInstance(); // 創建對象
dynamicMethod.invoke(instance); // 執行方法,輸出:getAge方法執行
核心價值
若后續需要切換為操作?Cat.java
(而非?Person.java
),只需修改配置文件的?reflect.className=reflect.Cat
,無需修改 Java 代碼 —— 這正是框架(如 Spring)“解耦” 的核心原理。
五、反射關鍵補充:類信息獲取與注意事項
5.1 獲取類的基本信息
通過類對象可快速獲取類的名稱、包名等信息,Test.java 中已實踐:
方法名 | 作用 | 示例(Person 類) |
---|---|---|
getName() | 獲取全類名(包名 + 類名) | pClass.getName() ?→ "reflect.Person" |
getSimpleName() | 獲取簡單類名(僅類名) |
|
5.2 反射的注意事項
反射雖強大,但存在以下問題,使用時需謹慎:
- 性能開銷:反射操作需在運行時解析類結構,比直接調用(如?
person.getAge()
)慢,高頻場景(如循環中)需避免。 - 安全風險:暴力反射突破了訪問權限限制,可能破壞類的封裝性(如修改私有變量),需確保操作的合理性。
- 代碼可讀性:反射代碼較抽象,不如直接調用直觀,需添加清晰注釋。
- 兼容性風險:若類結構修改(如方法名、參數列表變更),反射代碼可能拋出?
NoSuchMethodException
?等異常,需做好異常處理。
六、總結:反射知識體系圖譜
最后,用一張圖譜梳理本文核心內容,方便復習回顧:
Java 反射
├─ 核心前提:獲取類對象(3種方式)
│ ├─ Class.forName(全類名) → 動態加載
│ ├─ 類名.class → 靜態獲取
│ └─ 對象.getClass() → 實例反推
├─ 核心操作(3大組件)
│ ├─ 成員變量(Field):getDeclaredFields()/getFields() + set()/get() + 暴力反射
│ ├─ 成員方法(Method):getDeclaredMethods()/getMethods() + invoke() + 暴力反射
│ └─ 構造方法(Constructor):getDeclaredConstructors()/getConstructors() + newInstance()
├─ 實戰場景:結合配置文件動態加載
└─ 注意事項:性能、安全、可讀性、兼容性