反射的原理与代价是什么?setAccessible 有什么作用?
结论先行:反射让程序在运行时动态获取类结构并调用成员:由 ClassLoader 把字节码加载成 Class 对象,再通过 Method/Field/Constructor 描述符完成查找与调用。代价是性能更低(动态类型检查、参数包装、安全检查、无法被 JIT 内联)且破坏封装、出错难排查。setAccessible(true) 用于跳过 Java 语言级访问检查,从而操作 private 成员。
常用反射 API
| API | 作用 |
|---|---|
| Class.forName / 类.class / obj.getClass() | 获取 Class 对象 |
| getMethod / getDeclaredMethod | 获取方法(含继承与私有区别) |
| invoke(obj, args) | 反射调用目标方法 |
| setAccessible(true) | 关闭访问检查,可访问 private 成员 |
性能为什么慢、怎么优化
每次 invoke 都要做参数装箱与类型匹配、触发访问检查,且反射调用无法被 JIT 内联;高频反射应缓存 Method 对象,必要时改用 MethodHandle、LambdaMetafactory 或字节码生成(如 Spring 的 CGLIB 方案)。
提醒:setAccessible 只放行语言级访问,模块系统(JPMS)的强封装与安全管理器仍可能拦截越权反射。
import java.lang.reflect.Method;
public class ReflectDemo {
static class User {
private String name = "java";
private void hello() { System.out.println("hi " + name); }
}
public static void main(String[] args) throws Exception {
Class<?> clazz = User.class;
Method m = clazz.getDeclaredMethod("hello"); // 拿到私有方法
m.setAccessible(true); // 跳过访问检查
m.invoke(new User()); // 输出 hi java
}
}
常见追问 / 记忆点
- 记忆点:反射 = 运行时看类 + 动态调成员;setAccessible 只关“语言级”访问检查。
- 追问:JDK 9 模块化后强封装默认开启,越权反射会抛 InaccessibleObjectException。
- 追问:Spring 依赖注入、MyBatis 映射大量依赖反射,框架启动偏慢有一部分正源于此。
- 追问:被反复调用的反射会被 JVM 动态生成专用访问器加速(Inflation),所以它并非“慢到不可用”。