Java 反射

返回 面试 · 速查全集 Java常见面试题

运行时动态获取类信息(字段、方法、构造器)并操作对象,编译期无需知道具体类型。详解 反射


使用场景

  • Spring IOC:扫描类、注入依赖
  • MyBatis:Mapper 接口代理
  • 注解处理@Transactional@RequestMapping
  • 序列化 / 动态代理

获取 Class 的三种方式

Class<User> c1 = User.class;           // 类字面量
Class<?> c2 = user.getClass();         // 实例
Class<?> c3 = Class.forName("com.x.User");  // 全限定名,会触发初始化

同一类加载器下,同一个 Class 对象c1 == c2)。

方式是否触发初始化
.class
getClass()对象已创建
forName默认会初始化(forName(name, false, loader) 可不初始化)

类加载与双亲委派


核心 API

Constructor<User> c = clazz.getDeclaredConstructor(String.class);
c.setAccessible(true);
User u = c.newInstance("张三");
 
Method m = clazz.getDeclaredMethod("setName", String.class);
m.setAccessible(true);
m.invoke(u, "李四");
 
Field f = clazz.getDeclaredField("age");
f.setAccessible(true);
f.setInt(u, 20);

getXxx vs getDeclaredXxx

方法范围
getMethod / getFieldpublic,含父类
getDeclaredMethod / getDeclaredField本类全部(含 private),不含父类

访问 private 需 setAccessible(true)(JDK 9+ 模块系统可能受限)。


反射与泛型

运行时 List<String> 擦除为 List实例上拿不到元素类型;声明处可保留:

Type t = getClass().getGenericSuperclass();  // ParameterizedType
Type[] args = ((ParameterizedType) t).getActualTypeArguments();

运行时如何拿泛型类型


反射与注解

只有 @Retention(RetentionPolicy.RUNTIME) 的注解才能反射读取:

if (method.isAnnotationPresent(Transactional.class)) {
    Transactional tx = method.getAnnotation(Transactional.class);
}
Retention能否反射
SOURCE
CLASS
RUNTIME

优缺点

优点缺点
框架扩展、解耦性能低于直接调用
动态加载插件破坏封装(访问 private)
注解驱动编译期无法检查类型安全

性能开销:方法查找、装箱、权限检查。优化:缓存 Method/FieldsetAccessible(true);高频路径用 CGLIB/ByteBuddy 生成字节码。


常见面试题

Q:反射为什么慢?
A:字符串查找成员、可访问性检查、native/装箱;可缓存 Method 并关闭检查。

Q:Spring 如何用反射?
A:扫描 @Component、解析构造器/@Autowired 注入、处理 @PostConstruct、JDK/CGLIB 代理调用方法。

Q:.classClass.forName 区别?
A:后者指定类加载器加载并默认初始化类(执行 static 块)。

Q:能否反射修改 final 字段?
A:技术上可能(setAccessible + 去 final 修饰),但破坏语义;String 等不应依赖此 hack。

Q:反射和动态代理关系?
A:JDK 动态代理基于 InvocationHandler + 反射调用;CGLIB 用子类继承,目标方法仍可能反射调用。


相关笔记