Java反射机制允许运行时动态获取类信息并操作,核心是Class对象,获取方式有String.class(不初始化)、obj.getClass()(需实例)、Class.forName()(触发初始化);访问私有成员需setAccessible(true),但受安全管理器和JVM限制;反射性能差主因是参数检查、权限校验和无法JIT优化,建议缓存反射对象或改用MethodHandle。

Java反射机制,本质上是让程序在运行时“看清自己、修改自己”——不是编译时就知道类名和结构,而是在 new 之前,先通过 Class 对象把类的字段、方法、构造器甚至注解都摸清楚,再动态调用或赋值。
怎么拿到 Class 对象?三种方式差异很大
反射一切操作的起点是 Class 对象,但不同获取方式行为完全不同,选错会踩坑:
-
String.class:最轻量,不触发类初始化(static 块不执行),适合已知类型且无需初始化的场景 -
obj.getClass():安全可靠,但要求你已经有实例;注意数组类型返回的是[Ljava.lang.String;这类运行时表示,不是原始类 -
Class.forName("com.example.User"):唯一支持字符串加载类的方式,但默认会触发类初始化(执行 static 块 + 静态字段赋值)——很多启动慢、死锁问题就出在这儿
比如配置驱动的插件系统,必须用 forName;但单元测试中想绕过静态初始化干扰,就得换 .class 或提前 mock 类加载器。
调用私有方法/字段?setAccessible(true) 不是万能钥匙
想访问 private 成员,确实要调 method.setAccessible(true) 或 field.setAccessible(true),但现实更复杂:
立即学习“Java免费学习笔记(深入)”;
- JVM 安全管理器(SecurityManager)可能直接抛
SecurityException,尤其在受限容器(如老版 WebSphere、Applet)里 - 从 Java 12 起,
setAccessible对某些关键内部类(如java.lang.System的部分字段)会被 JVM 强制拒绝 - 修改
final字段后,JVM 可能因常量折叠优化仍返回旧值——得配合Unsafe.putObject或重新设置 volatile 标记才真正生效
简单示例:调用私有方法前务必加 try-catch,且别假设它一定成功:
try {
Method method = clazz.getDeclaredMethod("doSecretWork");
method.setAccessible(true);
method.invoke(instance);
} catch (ReflectiveOperationException e) {
// 可能是 NoSuchMethodException / IllegalAccessException / InaccessibleObjectException(Java 9+)
}
为什么反射慢?80 倍性能差到底卡在哪
直接调用 method.invoke() 比普通方法调用慢约 80 倍,主因不是“反射本身”,而是每次调用都要做三件事:
- 参数类型检查与自动装箱/拆箱(比如传
int却匹配Integer) - 访问权限校验(即使已 setAccessible,JVM 仍走安全检查路径)
- 方法句柄未内联,无法被 JIT 充分优化
实战建议:
- 缓存
Method、Field、Constructor对象,避免重复getDeclaredMethod() - 高频路径改用
MethodHandle(Java 7+),它跳过大部分反射开销,性能接近直接调用 - Spring 等框架内部对 Bean 属性读写已默认启用
Unsafe或字节码增强,而不是裸反射
真正难的从来不是“怎么用反射”,而是判断“该不该用”——当你的代码开始频繁 forName + invoke,就要问一句:是不是接口抽象没做好?或者配置设计把运行时耦合推给了反射?










