🌈个人主页 :一条泥憨鱼 (欢迎各位大佬莅临)
🎬精选专栏:数据结构与算法 ,JavaSE,苍穹外卖日记,AI学习

什么是反射?通俗理解
我们先想一个生活中的例子:你手里有一个神秘的黑盒子,不知道里面装了什么。正常情况下,你要先看说明书(源码),才知道它有哪些按钮、怎么用。
但如果你有一台"X光机",不用看说明书,直接对着黑盒子扫一下,就能看清楚里面有哪些零件、每个零件叫什么名字、怎么操作------这台"X光机"就是 Java 的反射机制。
// 官方定义Java 反射机制是指在程序运行时(Runtime),对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能,称为 Java 的反射机制。
关键词是"运行时"。普通代码在编译时就确定了用哪个类、调哪个方法。而反射可以在程序跑起来之后,才决定要操作哪个类------这带来了极大的灵活性,是 Spring、MyBatis 等主流框架的底层核心。
记忆技巧
正常写代码:编译期 就确定类和方法 → 安全、快速
使用反射:运行期才确定类和方法 → 灵活、强大
获取 Class 对象的三种方式
使用反射的第一步,是拿到目标类的 Class 对象 。你可以把 Class 对象理解为那台"X光机"扫描后得到的"类的说明书",里面记录了该类的一切信息。
Java 提供了三种方式来获取 Class 对象:
方式一:类名.class(最简单直接)
已知类名时使用,编译期就确定,不会抛出异常。
java
Class<String> clazz = String.class;
System.out.println(clazz); // 输出: class java.lang.String
方式二:对象.getClass()(已有对象时使用)
当你手里已经有一个对象实例,可以通过它获取对应的 Class。
java
String str = "Hello, 反射!";
Class<?> clazz = str.getClass();
System.out.println(clazz); // 输出: class java.lang.String
方式三:Class.forName("全类名")(最灵活,框架常用)
通过完整类名字符串 动态加载类。类名可以来自配置文件、数据库等,是框架最常用的方式。注意:需要处理**ClassNotFoundException**。
Java
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(示例类)
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(参数类型...) | 获取指定的任意构造方法 |
获取并使用构造方法
java
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("变量名") | 获取指定任意变量 |
获取并操作成员变量
java
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("方法名", 参数类型...) | 获取本类指定任意方法 |
获取并调用成员方法
java
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:
java
# 只需修改这里,程序行为就会改变
className=com.example.Student
methodName=study
使用反射读取配置并动态执行
java
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 等框架源码的前提。掌握本文的内容,你已经具备了探索主流框架底层原理的基础能力!
今天的学习就暂时告一段落啦,如果文章对您有用的话,还请留下一个免费的小心心和关注哦!
祝您工作顺利,生活愉快。我们下期再见!