深入理解Java反射(超详细)
2026/5/28 2:05:29 网站建设 项目流程

🌈个人主页:一条泥憨鱼(欢迎各位大佬莅临)

🎬精选专栏:数据结构与算法,JavaSE ,苍穹外卖日记,AI学习

​​​​​什么是反射?通俗理解

我们先想一个生活中的例子:你手里有一个神秘的黑盒子,不知道里面装了什么。正常情况下,你要先看说明书(源码),才知道它有哪些按钮、怎么用。

但如果你有一台"X光机",不用看说明书,直接对着黑盒子扫一下,就能看清楚里面有哪些零件、每个零件叫什么名字、怎么操作——这台"X光机"就是 Java 的反射机制

// 官方定义Java 反射机制是指在程序运行时(Runtime),对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能,称为 Java 的反射机制。

关键词是"运行时"。普通代码在编译时就确定了用哪个类、调哪个方法。而反射可以在程序跑起来之后,才决定要操作哪个类——这带来了极大的灵活性,是 Spring、MyBatis 等主流框架的底层核心。

记忆技巧

正常写代码:编译期就确定类和方法 → 安全、快速
使用反射:运行期才确定类和方法 → 灵活、强大


获取 Class 对象的三种方式

使用反射的第一步,是拿到目标类的Class对象。你可以把Class对象理解为那台"X光机"扫描后得到的"类的说明书",里面记录了该类的一切信息。

Java 提供了三种方式来获取Class对象:

方式一:类名.class(最简单直接)

已知类名时使用,编译期就确定,不会抛出异常。

Class<String> clazz = String.class; System.out.println(clazz); // 输出: class java.lang.String

方式二:对象.getClass()(已有对象时使用)

当你手里已经有一个对象实例,可以通过它获取对应的Class

String str = "Hello, 反射!"; Class<?> clazz = str.getClass(); System.out.println(clazz); // 输出: class java.lang.String

方式三:Class.forName("全类名")(最灵活,框架常用)

通过完整类名字符串动态加载类。类名可以来自配置文件、数据库等,是框架最常用的方式。注意:需要处理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 对象)。
三种方式的结果完全相同:String.class == str.getClass() == Class.forName("java.lang.String")


利用反射获取构造方法

拿到 Class 对象后,我们就可以"解剖"这个类了。先来看如何获取构造方法(Constructor)

首先,我们定义一个示例类Student,后续所有例子都基于它:

Java · Student.java(示例类)

public class Student { // 成员变量 public String name; private int age; // 无参构造 public Student() {} // 有参构造 public Student(String name, int age) { this.name = name; this.age = age; } // 成员方法 public void study() { System.out.println(name + " 正在学习Java!"); } private void sleep() { System.out.println(name + " 在睡觉..."); } // getter/setter 省略... }

常用的获取构造方法 API:

方法说明
getConstructors()获取所有public构造方法
getDeclaredConstructors()获取所有构造方法(包括私有)
getConstructor(参数类型...)获取指定的 public 构造方法
getDeclaredConstructor(参数类型...)获取指定的任意构造方法

获取并使用构造方法

import java.lang.reflect.Constructor; Class<?> clazz = Class.forName("Student"); // 1. 获取所有 public 构造方法 Constructor[] constructors = clazz.getConstructors(); for (Constructor c : constructors) { System.out.println(c); } // 2. 获取有参构造并创建对象 Constructor<?> con = clazz.getConstructor(String.class, int.class); Object stu = con.newInstance("张三", 18); // 等价于 new Student("张三", 18) System.out.println(stu); // 3. 使用无参构造创建对象(简便写法) Object stu2 = clazz.getDeclaredConstructor().newInstance(); System.out.println(stu2);

小贴士

con.newInstance(参数...)就是反射版的new关键字。参数类型和顺序必须与构造方法一致。


利用反射获取成员变量

反射不仅能创建对象,还能直接读写对象的成员变量(Field),即使是private修饰的私有变量也能访问!

方法说明
getFields()获取所有public成员变量
getDeclaredFields()获取所有成员变量(包括私有)
getField("变量名")获取指定 public 变量
getDeclaredField("变量名")获取指定任意变量

获取并操作成员变量

import java.lang.reflect.Field; Class<?> clazz = Student.class; // 先创建一个 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 // ⚠️ 访问私有变量必须先调用 setAccessible(true) 暴力破解! Field ageField = clazz.getDeclaredField("age"); ageField.setAccessible(true); // 取消私有访问限制(关键!) ageField.set(stu, 20); // 修改 age 值 System.out.println(ageField.get(stu)); // 读取 age 值 → 20

⚠️ 重点:setAccessible(true)访问private成员时,必须先调用setAccessible(true),否则会抛出IllegalAccessException。这个操作相当于"强制解锁私有权限",在框架中非常常见,但在业务代码中要谨慎使用。


利用反射获取成员方法

反射还可以获取并调用任意方法(Method),包括私有方法,甚至可以在不知道方法名的情况下,通过字符串来动态调用。

方法说明
getMethods()获取所有 public 方法(含父类继承的)
getDeclaredMethods()获取本类所有方法(不含继承,含私有)
getMethod("方法名", 参数类型...)获取指定 public 方法
getDeclaredMethod("方法名", 参数类型...)获取本类指定任意方法

获取并调用成员方法

import java.lang.reflect.Method; Class<?> clazz = Student.class; Object stu = clazz.getDeclaredConstructor(String.class, int.class) .newInstance("张三", 18); // 1. 调用无参 public 方法 study() Method studyMethod = clazz.getMethod("study"); studyMethod.invoke(stu); // 输出:张三 正在学习Java! // 2. 调用带参方法(假设有 setName(String name) 方法) Method setNameMethod = clazz.getMethod("setName", String.class); setNameMethod.invoke(stu, "王五"); // 等价于 stu.setName("王五") // 3. 调用 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); // 年龄: 18

method.invoke()

参数说明method.invoke(obj, arg1, arg2, ...)
第一个参数:要调用方法的对象实例
后续参数:方法所需的实际参数值
返回值:方法的返回值(如果是 void,则返回 null)


反射的实际作用与应用场景

你可能会问:"我直接new一个对象不就好了,为什么要用这么麻烦的反射?"

反射的真正价值,在于"当你不知道要用哪个类的时候"。来看一个简单的框架模拟案例:

案例:模拟框架——根据配置文件动态创建对象

假设我们有一个配置文件config.properties

# 只需修改这里,程序行为就会改变 className=com.example.Student methodName=study

使用反射读取配置并动态执行

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. 使用反射动态创建对象并调用方法 // ✅ 不修改 Java 代码,只改配置文件就能切换行为! Class<?> clazz = Class.forName(className); Object obj = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getMethod(methodName); method.invoke(obj); } }

现在把配置文件里的className改成com.example.Teacher,程序就会操作 Teacher 类,完全不需要修改 Java 代码重新编译。这正是 Spring 框架依赖注入(IoC)的基本原理!

反射在主流框架中的应用

Spring IoC:读取 XML / 注解配置 → 用反射创建 Bean 对象并注入依赖
MyBatis:读取 SQL 结果集 → 用反射把列值赋给 Java 对象的字段
Jackson / Gson:JSON 反序列化 → 用反射把 JSON 字段映射到 Java 对象
JUnit:扫描带 @Test 注解的方法 → 用反射自动执行测试方法


反射的优缺点总结

✅ 优点

灵活性强:运行时动态操作类,无需在编译期确定类型,是框架设计的基石。

✅ 优点

降低耦合:通过配置文件驱动,代码与具体类解耦,修改配置即可切换实现。

⚠️ 缺点

性能开销:反射涉及动态解析,比直接调用慢,频繁调用场景应缓存 Method / Field 对象。

⚠️ 缺点

安全风险:setAccessible(true)可绕过访问控制,破坏封装性,需谨慎使用。

一张图回顾全部知识点

Java 反射 ├── 获取 Class 对象 │ ├── 类名.class ← 编译期,最安全 │ ├── 对象.getClass() ← 有对象实例时用 │ └── Class.forName("全类名") ← 运行时,最灵活(框架常用) │ ├── 获取构造方法 (Constructor) │ ├── getConstructors() ← 所有 public 构造 │ ├── getDeclaredConstructors() ← 所有构造(含 private) │ └── con.newInstance(参数) ← 创建对象 │ ├── 获取成员变量 (Field) │ ├── getFields() ← 所有 public 字段 │ ├── getDeclaredFields() ← 所有字段(含 private) │ ├── field.get(obj) ← 读取值 │ ├── field.set(obj, val) ← 修改值 │ └── setAccessible(true) ← 访问 private 必须调用! │ ├── 获取成员方法 (Method) │ ├── getMethods() ← 所有 public 方法(含继承) │ ├── getDeclaredMethods() ← 本类所有方法(含 private) │ ├── method.invoke(obj, args...) ← 调用方法 │ └── setAccessible(true) ← 调用 private 必须调用! │ └── 应用场景 ├── Spring IoC 依赖注入 ├── MyBatis ORM 映射 ├── JSON 序列化 / 反序列化 └── 单元测试框架(JUnit)

反射是"高阶武器",初学阶段不需要在业务代码中大量使用。但理解反射是读懂 Spring、MyBatis 等框架源码的前提。掌握本文的内容,你已经具备了探索主流框架底层原理的基础能力!

今天的学习就暂时告一段落啦,如果文章对您有用的话,还请留下一个免费的小心心和关注哦!

祝您工作顺利,生活愉快。我们下期再见!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询