MD 状态:🌱 分类:编程与语言 更新:2026/5/29

反射机制

[!abstract] 一句话定义 反射是Java在运行时获取类信息(字段、方法、构造器)并动态操作它们的能力,让程序能够”审视自身”。

为什么需要它?

假设你正在写一个通用的对象转JSON工具:

// 没有反射:每种类型都要写一遍
public String toJson(User user) {
    return "{\"name\":\"" + user.getName() + "\",\"age\":" + user.getAge() + "}";
}

public String toJson(Product product) {
    return "{\"title\":\"" + product.getTitle() + "\",\"price\":" + product.getPrice() + "}";
}

// 有N种类型就要写N个方法...这显然不行

痛点

  • 编译时必须知道具体类型,无法写”通用代码”
  • 框架无法处理”未知类型”,每个类都要硬编码
  • 无法在运行时动态发现和使用新加载的类

有了反射,一个方法搞定所有类型:

public String toJson(Object obj) {
    Class<?> clazz = obj.getClass();  // 运行时获取类型
    for (Field field : clazz.getDeclaredFields()) {  // 遍历所有字段
        field.setAccessible(true);
        // 动态读取字段值...
    }
}

核心直觉

类比:反射是”X光透视”

  • 普通代码:你知道对方是”张三”,直接叫他名字
  • 反射代码:你不知道对方是谁,但可以用X光看到他的内部结构(有几个器官、怎么连接),然后操作它

反射让你在编译时不需要知道具体类型,运行时再”透视”它的结构。

它是怎么工作的?

反射的核心流程

flowchart TD
    A[获取Class对象] --> B[查看类结构]
    B --> C[动态操作]

    A --> A1[obj.getClass]
    A --> A2[Class.forName]
    A --> A3[类名.class]

    B --> B1[getFields<br/>获取字段]
    B --> B2[getMethods<br/>获取方法]
    B --> B3[getConstructors<br/>获取构造器]

    C --> C1[field.get/set<br/>读写字段值]
    C --> C2[method.invoke<br/>调用方法]
    C --> C3[constructor.newInstance<br/>创建对象]

    style A fill:#ff9999
    style B fill:#99ccff
    style C fill:#99ff99

反射的三个入口

// 方式1:通过对象获取
User user = new User();
Class<?> clazz = user.getClass();

// 方式2:通过类名获取(最常用)
Class<?> clazz = User.class;

// 方式3:通过全限定名获取(框架常用)
Class<?> clazz = Class.forName("com.example.User");

动态操作示例

Class<?> clazz = Class.forName("com.example.User");

// 1. 创建对象(替代 new User())
Object obj = clazz.getDeclaredConstructor().newInstance();

// 2. 读写字段(替代 user.getName())
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);  // 突破private限制
nameField.set(obj, "张三");
String name = (String) nameField.get(obj);

// 3. 调用方法(替代 user.sayHello())
Method method = clazz.getMethod("sayHello", String.class);
method.invoke(obj, "你好");

关键组件 / 核心要素

组件作用类比
Class<?>类的元信息载体身份证档案
Field字段信息身体器官
Method方法信息身体能力
Constructor构造器信息出生方式
setAccessible(true)突破访问控制获取管理员权限

与相关概念的关系

[!info] vs 编译时绑定

  • 编译时绑定:类型在编译期确定,性能好,但不灵活
  • 反射:类型在运行时确定,灵活,但有性能开销
  • 大部分业务代码用编译时绑定,框架和工具用反射

[!note] 依赖于 类加载机制

  • 反射的前提是类已经被加载到JVM
  • Class.forName() 会触发类加载
  • 理解类加载有助于理解反射的边界

[!tip] 被 Spring注解 使用

  • Spring通过反射读取注解信息
  • @Autowired@Component 等都依赖反射实现
  • 没有反射就没有Spring IoC

[!tip] 扩展为 动态代理

  • JDK动态代理基于反射实现
  • Spring AOP的底层就是动态代理

典型应用场景

  • 框架开发 — Spring IoC通过反射创建Bean、注入依赖
  • 注解处理 — 运行时读取注解元数据
  • ORM框架 — MyBatis/Hibernate将数据库记录映射到Java对象
  • 序列化 — JSON/XML与Java对象互转
  • 测试框架 — JUnit通过反射发现和执行测试方法
  • IDE功能 — 代码补全、跳转到定义都依赖反射获取类型信息

常见误解与陷阱

[!danger] ❌ 误以为:反射性能很差,不能用 ✅ 实际上:反射确实比直接调用慢(约10-50倍),但在框架初始化阶段执行一次,对整体性能影响很小。性能敏感的热路径避免使用即可

[!danger] ❌ 误以为:反射可以突破一切限制 ✅ 实际上:Java模块化系统(JPMS)对反射有限制,setAccessible(true) 在高版本Java中可能被拒绝

[!danger] ❌ 误以为:反射是”高级特性”,普通开发者不需要懂 ✅ 实际上:理解反射才能理解Spring等框架的工作原理,遇到问题才能调试

[!danger] ❌ 误以为:Class.forName()类.class 完全一样 ✅ 实际上:Class.forName() 会触发类的静态初始化块,类.class 不会

延伸阅读

  • 想深入理解原理 → 学习JVM类加载机制和字节码
  • 想看工程实践 → 阅读Spring IoC容器源码,看反射如何被使用
  • 想了解前沿进展 → 关注Java记录类型(Record)、模式匹配对反射的替代

关联笔记

前置知识Java基础 · 类与对象 · 访问控制 同族概念注解 · 泛型 · 动态代理 应用场景Spring框架 · Spring注解 · ORM框架