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

