Java反射是运行时动态获取类信息并操作对象的机制,核心用于框架解耦(如Spring动态加载@Service)、通用功能实现(如MyBatis方法匹配)、注解处理(@Autowired字段注入)及测试工具开发,其API调用需严格遵循Class→Method/Field→setAccessible→invoke流程,并注意性能损耗、安全限制及泛型擦除等陷阱。

Java 反射是面试高频考点,尤其在中高级岗位和框架原理类问题中几乎必问。写好深度复习题,关键不是堆砌概念,而是紧扣“为什么用、怎么用、有什么坑、怎么避坑”这四条主线,把原理、API、实战、陷阱串成逻辑链。
一、从本质出发:反射到底在解决什么问题?
不能只答“运行时获取类信息”。要讲清楚场景驱动力:
- 框架需要解耦:Spring 不可能在编译期就知道你写的@Service类叫什么,只能靠 Class.forName("xxx.UserService") 动态加载
- 通用性需求:MyBatis 的 SqlSession.selectOne("userMapper.findById", 100) —— 方法名和参数类型全靠反射匹配
- 测试与调试工具:IDE 的类浏览器、JUnit 的 @Test 方法查找,底层都依赖 getDeclaredMethods()
- 注解处理:@Autowired、@Value 这些字段注入,本质是反射拿到 Field 对象,再 setAccessible(true) 赋值
二、核心 API 必须能手写调用链
面试官常让白板写代码,重点考察是否真懂流程而非死记。以“调用私有方法”为例,必须写出完整且健壮的步骤:
- 获取 Class 对象(优先用 User.class,不用 forName,除非类名来自配置)
- 用 getDeclaredMethod("methodName", String.class)(不是 getMethod,后者不找 private)
- setAccessible(true) —— 这步漏掉直接抛 IllegalAccessException
- invoke 时第一个参数是实例对象(静态方法传 null),参数类型和个数必须严格匹配
- 捕获 InvocationTargetException,并用 getCause() 拿到原始异常(比如被调方法里 throw new RuntimeException)
三、高频陷阱题必须讲清边界条件
深度体现在对“例外情况”的掌握:
立即学习“Java免费学习笔记(深入)”;
- 修改 final 字段是否生效?:可以 set,但 static final 编译期常量(如 public static final int MAX = 100)会被内联,改了 field 值,代码里读的还是 100
- 数组类型怎么获取 Class?:int[].class、String[][].class 合法;new int[0].getClass() 返回 [I;Class.forName("[Ljava.lang.String;") 是字符串数组的写法
-
泛型擦除下如何获取真实类型?:用 getGenericSuperclass() + TypeCast,比如 List
的父类是 ParameterizedType,再强转取实际类型参数 - newInstance() 为什么被废弃?:它只调无参构造,且无法处理构造器抛异常的情况;现在统一用 getDeclaredConstructor(...).newInstance(...)
四、性能与安全必须量化说明
不能只说“慢”“不安全”,要给出可感知的对比:
- 普通方法调用 vs 反射调用:JMH 测试显示,简单 getter 在 HotSpot 下反射开销约是直接调用的 3–5 倍;频繁调用需缓存 Method 对象
- setAccessible(true) 的代价:首次调用会触发 JVM 安全检查绕过机制,后续调用快,但破坏了模块封装契约
- SecurityManager 已在 Java 17+ 默认移除,但企业级容器(如 OSGi、某些云平台沙箱)仍可能拦截反射调用,需预判兼容性
不复杂但容易忽略:真正体现深度的,是你能否在解释完原理后,自然带出一句——“所以 Spring 早期用 CGLIB 做代理,后来 JDK 动态代理成为主流,正是因为反射 API 稳定且无需字节码操作”。这才是把点连成线。


















