java反射是jvm提供的元编程能力,通过class对象动态获取并操作类结构,但存在性能、安全和维护性问题,应谨慎使用。

反射就是让Java程序在运行时“照镜子”
Java反射机制的本质,是程序在 JVM 运行过程中,通过 Class 对象反向读取已加载类的字节码信息(.class),从而动态获取类名、字段、方法、构造器等结构,并能绕过编译期检查去实例化对象、调用私有方法、修改私有字段。它不是语法糖,而是 JVM 提供的元编程能力,依赖的是类加载后生成的运行时类型信息(RTTI)。
获取 Class 对象的三种方式,行为完全不同
看似都能拿到 Class,但触发时机和副作用差异极大,选错会引发类初始化异常或静态块意外执行:
-
String.class:最轻量,不触发类初始化,适用于编译期已知类型的场景 -
obj.getClass():安全可靠,但前提是已有实例;注意泛型擦除后返回的是实际运行时类型(如ArrayList<string>.getClass()</string>返回ArrayList.class) -
Class.forName("com.example.Foo"):默认会触发类的初始化(执行static块和静态字段赋值),若目标类初始化失败(比如数据库连接未就绪),这里直接抛ExceptionInInitializerError
调用私有方法/访问私有字段,setAccessible(true) 不是万能钥匙
它只是关闭 JVM 的访问检查(AccessCheck),但不解决语义合法性问题:
- 对
final字段调用Field.set()会静默失败(值不变),除非先用反射把modifiers字段设为非final(极不推荐,破坏不可变性) - 调用被
SecurityManager限制的私有 API(如sun.misc.Unsafe)时,setAccessible(true)仍可能被拒绝,报SecurityException - 从 Java 12 起,JVM 默认启用强封装(
--illegal-access=deny),反射访问 JDK 内部 API 会直接失败,必须加启动参数临时放开(如--add-opens java.base/java.lang=ALL-UNNAMED)
为什么 Spring 和 MyBatis 离不开反射,但你不该随便用
框架靠反射实现解耦:Spring 用 Class.forName() + Constructor.newInstance()(或更安全的 Constructor.newInstance(...))创建 Bean;MyBatis 用 Field.setAccessible(true) + Field.set() 把 ResultSet 值塞进实体类私有字段。但你自己写业务代码时要注意:
立即学习“Java免费学习笔记(深入)”;
- 反射调用比直接调用慢 5–10 倍以上,高频路径(如循环内、RPC 入口)务必避免
- IDE 和静态分析工具无法校验反射调用的目标是否存在,重构时极易漏改,导致运行时报
NoSuchMethodException或IllegalAccessException - 模块化(JPMS)下,跨模块反射需显式
open包,否则ClassNotFoundException不是类没找到,而是模块没导出
真正需要反射的地方其实很窄:通用序列化器、测试 Mock 工具、插件热加载——其余多数情况,用接口+策略模式、工厂方法或注解处理器更稳。










