深入理解Java反射(超詳細(xì)教程)
?????什么是反射?通俗理解
我們先想一個生活中的例子:你手里有一個神秘的黑盒子,不知道里面裝了什么。正常情況下,你要先看說明書(源碼),才知道它有哪些按鈕、怎么用。
但如果你有一臺"X光機(jī)",不用看說明書,直接對著黑盒子掃一下,就能看清楚里面有哪些零件、每個零件叫什么名字、怎么操作——這臺"X光機(jī)"就是 Java 的反射機(jī)制。
// 官方定義Java 反射機(jī)制是指在程序運行時(Runtime),對于任意一個類,都能知道這個類的所有屬性和方法;對于任意一個對象,都能調(diào)用它的任意方法和屬性。這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能,稱為 Java 的反射機(jī)制。
關(guān)鍵詞是"運行時"。普通代碼在編譯時就確定了用哪個類、調(diào)哪個方法。而反射可以在程序跑起來之后,才決定要操作哪個類——這帶來了極大的靈活性,是 Spring、MyBatis 等主流框架的底層核心。
記憶技巧
正常寫代碼:編譯期就確定類和方法 → 安全、快速
使用反射:運行期才確定類和方法 → 靈活、強(qiáng)大
獲取 Class 對象的三種方式
使用反射的第一步,是拿到目標(biāo)類的 Class 對象。你可以把 Class 對象理解為那臺"X光機(jī)"掃描后得到的"類的說明書",里面記錄了該類的一切信息。
Java 提供了三種方式來獲取 Class 對象:
方式一:類名.class(最簡單直接)
已知類名時使用,編譯期就確定,不會拋出異常。
Class<String> clazz = String.class; System.out.println(clazz); // 輸出: class java.lang.String
方式二:對象.getClass()(已有對象時使用)
當(dāng)你手里已經(jīng)有一個對象實例,可以通過它獲取對應(yīng)的 Class。
String str = "Hello, 反射!"; Class<?> clazz = str.getClass(); System.out.println(clazz); // 輸出: class java.lang.String
方式三:Class.forName("全類名")(最靈活,框架常用)
通過完整類名字符串動態(tài)加載類。類名可以來自配置文件、數(shù)據(jù)庫等,是框架最常用的方式。注意:需要處理 ClassNotFoundException。
Java
try {
// 全類名 = 包名 + 類名
Class<?> clazz = Class.forName("java.lang.String");
System.out.println(clazz); // 輸出: class java.lang.String
} catch (ClassNotFoundException e) {
e.printStackTrace();
}重要提示
同一個類,無論用哪種方式獲取
Class對象,得到的都是同一個對象(JVM 中每個類只有一份 Class 對象)。
三種方式的結(jié)果完全相同:String.class == str.getClass() == Class.forName("java.lang.String")
利用反射獲取構(gòu)造方法
拿到 Class 對象后,我們就可以"解剖"這個類了。先來看如何獲取構(gòu)造方法(Constructor)。
首先,我們定義一個示例類 Student,后續(xù)所有例子都基于它:
Java · Student.java(示例類)
public class Student {
// 成員變量
public String name;
private int age;
// 無參構(gòu)造
public Student() {}
// 有參構(gòu)造
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 成員方法
public void study() {
System.out.println(name + " 正在學(xué)習(xí)Java!");
}
private void sleep() {
System.out.println(name + " 在睡覺...");
}
// getter/setter 省略...
}常用的獲取構(gòu)造方法 API:
| 方法 | 說明 |
|---|---|
| getConstructors() | 獲取所有 public 構(gòu)造方法 |
| getDeclaredConstructors() | 獲取所有構(gòu)造方法(包括私有) |
| getConstructor(參數(shù)類型...) | 獲取指定的 public 構(gòu)造方法 |
| getDeclaredConstructor(參數(shù)類型...) | 獲取指定的任意構(gòu)造方法 |
獲取并使用構(gòu)造方法
import java.lang.reflect.Constructor;
Class<?> clazz = Class.forName("Student");
// 1. 獲取所有 public 構(gòu)造方法
Constructor[] constructors = clazz.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
// 2. 獲取有參構(gòu)造并創(chuàng)建對象
Constructor<?> con = clazz.getConstructor(String.class, int.class);
Object stu = con.newInstance("張三", 18); // 等價于 new Student("張三", 18)
System.out.println(stu);
// 3. 使用無參構(gòu)造創(chuàng)建對象(簡便寫法)
Object stu2 = clazz.getDeclaredConstructor().newInstance();
System.out.println(stu2);小貼士
con.newInstance(參數(shù)...)就是反射版的new關(guān)鍵字。參數(shù)類型和順序必須與構(gòu)造方法一致。
利用反射獲取成員變量
反射不僅能創(chuàng)建對象,還能直接讀寫對象的成員變量(Field),即使是 private 修飾的私有變量也能訪問!
| 方法 | 說明 |
|---|---|
| getFields() | 獲取所有 public 成員變量 |
| getDeclaredFields() | 獲取所有成員變量(包括私有) |
| getField("變量名") | 獲取指定 public 變量 |
| getDeclaredField("變量名") | 獲取指定任意變量 |
獲取并操作成員變量
import java.lang.reflect.Field;
Class<?> clazz = Student.class;
// 先創(chuàng)建一個 Student 對象用于測試
Object stu = clazz.getDeclaredConstructor(String.class, int.class)
.newInstance("張三", 18);
// 1. 獲取 public 變量 name 并修改
Field nameField = clazz.getField("name");
nameField.set(stu, "李四"); // 修改 name 值
System.out.println(nameField.get(stu)); // 讀取 name 值 → 李四
// 2. 獲取 private 變量 age
// ?? 訪問私有變量必須先調(diào)用 setAccessible(true) 暴力破解!
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 取消私有訪問限制(關(guān)鍵?。?
ageField.set(stu, 20); // 修改 age 值
System.out.println(ageField.get(stu)); // 讀取 age 值 → 20?? 重點:setAccessible(true)訪問
private成員時,必須先調(diào)用setAccessible(true),否則會拋出IllegalAccessException。這個操作相當(dāng)于"強(qiáng)制解鎖私有權(quán)限",在框架中非常常見,但在業(yè)務(wù)代碼中要謹(jǐn)慎使用。
利用反射獲取成員方法
反射還可以獲取并調(diào)用任意方法(Method),包括私有方法,甚至可以在不知道方法名的情況下,通過字符串來動態(tài)調(diào)用。
| 方法 | 說明 |
|---|---|
| getMethods() | 獲取所有 public 方法(含父類繼承的) |
| getDeclaredMethods() | 獲取本類所有方法(不含繼承,含私有) |
| getMethod("方法名", 參數(shù)類型...) | 獲取指定 public 方法 |
| getDeclaredMethod("方法名", 參數(shù)類型...) | 獲取本類指定任意方法 |
獲取并調(diào)用成員方法
import java.lang.reflect.Method;
Class<?> clazz = Student.class;
Object stu = clazz.getDeclaredConstructor(String.class, int.class)
.newInstance("張三", 18);
// 1. 調(diào)用無參 public 方法 study()
Method studyMethod = clazz.getMethod("study");
studyMethod.invoke(stu); // 輸出:張三 正在學(xué)習(xí)Java!
// 2. 調(diào)用帶參方法(假設(shè)有 setName(String name) 方法)
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(stu, "王五"); // 等價于 stu.setName("王五")
// 3. 調(diào)用 private 方法 sleep()
Method sleepMethod = clazz.getDeclaredMethod("sleep");
sleepMethod.setAccessible(true); // 私有方法同樣需要暴力破解
sleepMethod.invoke(stu); // 輸出:王五 在睡覺...
// 4. 獲取方法的返回值
Method getAgeMethod = clazz.getMethod("getAge");
Object result = getAgeMethod.invoke(stu);
System.out.println("年齡: " + result); // 年齡: 18method.invoke()
參數(shù)說明
method.invoke(obj, arg1, arg2, ...)
第一個參數(shù):要調(diào)用方法的對象實例
后續(xù)參數(shù):方法所需的實際參數(shù)值
返回值:方法的返回值(如果是 void,則返回 null)
反射的實際作用與應(yīng)用場景
你可能會問:"我直接 new 一個對象不就好了,為什么要用這么麻煩的反射?"
反射的真正價值,在于"當(dāng)你不知道要用哪個類的時候"。來看一個簡單的框架模擬案例:
案例:模擬框架——根據(jù)配置文件動態(tài)創(chuàng)建對象
假設(shè)我們有一個配置文件 config.properties:
# 只需修改這里,程序行為就會改變 className=com.example.Student methodName=study
使用反射讀取配置并動態(tài)執(zhí)行
import java.util.Properties;
import java.io.InputStream;
import java.lang.reflect.*;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
// 1. 讀取配置文件
Properties prop = new Properties();
InputStream is = ReflectDemo.class.getClassLoader()
.getResourceAsStream("config.properties");
prop.load(is);
// 2. 獲取類名和方法名
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
// 3. 使用反射動態(tài)創(chuàng)建對象并調(diào)用方法
// ? 不修改 Java 代碼,只改配置文件就能切換行為!
Class<?> clazz = Class.forName(className);
Object obj = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(methodName);
method.invoke(obj);
}
}現(xiàn)在把配置文件里的 className 改成 com.example.Teacher,程序就會操作 Teacher 類,完全不需要修改 Java 代碼重新編譯。這正是 Spring 框架依賴注入(IoC)的基本原理!
反射在主流框架中的應(yīng)用
Spring IoC:讀取 XML / 注解配置 → 用反射創(chuàng)建 Bean 對象并注入依賴
MyBatis:讀取 SQL 結(jié)果集 → 用反射把列值賦給 Java 對象的字段
Jackson / Gson:JSON 反序列化 → 用反射把 JSON 字段映射到 Java 對象
JUnit:掃描帶 @Test 注解的方法 → 用反射自動執(zhí)行測試方法
反射的優(yōu)缺點總結(jié)
? 優(yōu)點
靈活性強(qiáng):運行時動態(tài)操作類,無需在編譯期確定類型,是框架設(shè)計的基石。
? 優(yōu)點
降低耦合:通過配置文件驅(qū)動,代碼與具體類解耦,修改配置即可切換實現(xiàn)。
?? 缺點
性能開銷:反射涉及動態(tài)解析,比直接調(diào)用慢,頻繁調(diào)用場景應(yīng)緩存 Method / Field 對象。
?? 缺點
安全風(fēng)險:
setAccessible(true)可繞過訪問控制,破壞封裝性,需謹(jǐn)慎使用。
一張圖回顧全部知識點
Java 反射
├── 獲取 Class 對象
│ ├── 類名.class ← 編譯期,最安全
│ ├── 對象.getClass() ← 有對象實例時用
│ └── Class.forName("全類名") ← 運行時,最靈活(框架常用)
│
├── 獲取構(gòu)造方法 (Constructor)
│ ├── getConstructors() ← 所有 public 構(gòu)造
│ ├── getDeclaredConstructors() ← 所有構(gòu)造(含 private)
│ └── con.newInstance(參數(shù)) ← 創(chuàng)建對象
│
├── 獲取成員變量 (Field)
│ ├── getFields() ← 所有 public 字段
│ ├── getDeclaredFields() ← 所有字段(含 private)
│ ├── field.get(obj) ← 讀取值
│ ├── field.set(obj, val) ← 修改值
│ └── setAccessible(true) ← 訪問 private 必須調(diào)用!
│
├── 獲取成員方法 (Method)
│ ├── getMethods() ← 所有 public 方法(含繼承)
│ ├── getDeclaredMethods() ← 本類所有方法(含 private)
│ ├── method.invoke(obj, args...) ← 調(diào)用方法
│ └── setAccessible(true) ← 調(diào)用 private 必須調(diào)用!
│
└── 應(yīng)用場景
├── Spring IoC 依賴注入
├── MyBatis ORM 映射
├── JSON 序列化 / 反序列化
└── 單元測試框架(JUnit)反射是"高階武器",初學(xué)階段不需要在業(yè)務(wù)代碼中大量使用。但理解反射是讀懂 Spring、MyBatis 等框架源碼的前提。掌握本文的內(nèi)容,你已經(jīng)具備了探索主流框架底層原理的基礎(chǔ)能力!
到此這篇關(guān)于深入理解Java反射(超詳細(xì)教程)的文章就介紹到這了,更多相關(guān)Java反射內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Java中OutOfMemoryError問題產(chǎn)生原因
本文主要介紹了淺談Java中OutOfMemoryError問題產(chǎn)生原因,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06

