反射

返回 Java 基础
→ 相关:类加载(Class 对象由类加载器生成)· JUC(反射与线程安全)

是什么

反射允许程序在运行时动态获取类的信息(字段、方法、构造器),并操作对象,而不需要在编译期知道类的具体类型。

使用场景:框架(Spring IOC、MyBatis)、注解处理、序列化、动态代理。


获取 Class 对象的三种方式

// 1. 类字面量(编译期确定,不触发初始化)
Class<Person> c1 = Person.class;
 
// 2. 对象的 getClass()(运行时)
Person p = new Person();
Class<?> c2 = p.getClass();
 
// 3. Class.forName()(运行时,会触发[[类加载|类初始化]])
Class<?> c3 = Class.forName("com.example.Person");

同一个类的三种方式获取到的是同一个 Class 对象c1 == c2 == c3)。


常用 API

获取构造器 & 创建对象

Class<Person> clazz = Person.class;
 
// 获取 public 无参构造
Constructor<Person> c = clazz.getConstructor();
Person p = c.newInstance();
 
// 获取任意构造(含 private)
Constructor<Person> c2 = clazz.getDeclaredConstructor(String.class, int.class);
c2.setAccessible(true);  // 忽略访问限制
Person p2 = c2.newInstance("张三", 18);

获取方法 & 调用

// getMethod:public 方法(含继承的)
Method method = clazz.getMethod("getName");
String name = (String) method.invoke(p);
 
// getDeclaredMethod:本类声明的所有方法(含 private)
Method privateMethod = clazz.getDeclaredMethod("secret");
privateMethod.setAccessible(true);
privateMethod.invoke(p);

获取字段 & 读写

// getDeclaredField:本类声明的字段(含 private)
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
 
field.set(p, "李四");       // 设置值
String val = (String) field.get(p);  // 读取值

getDeclared vs 不带 Declared

方法范围访问权限
getMethodpublic 方法(含父类)只有 public
getDeclaredMethod本类所有方法任意(需 setAccessible)
getFieldpublic 字段(含父类)只有 public
getDeclaredField本类所有字段任意(需 setAccessible)

反射与泛型

泛型在编译后会类型擦除,运行时无法直接通过反射获取泛型类型参数。但可以通过 ParameterizedType 获取声明处的泛型:

// 获取父类的泛型参数
Type superClass = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) superClass;
Type[] args = pt.getActualTypeArguments();  // [String.class]

Spring 的 ResolvableType、Jackson 的 TypeReference 都是利用这个原理。


性能问题与优化

反射比直接调用慢,主要开销:

  1. 方法查找(字符串匹配)
  2. 参数装箱/拆箱
  3. 安全检查

优化手段

  • 缓存 Method/Field 对象,不要每次都反射查找
  • 调用 setAccessible(true) 跳过安全检查
  • 高频场景考虑用动态代理ASM/ByteBuddy 生成字节码替代反射

反射与注解

反射是读取注解的基础:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)  // 必须是 RUNTIME 才能被反射读取
public @interface Log {}
 
// 读取注解
Method method = clazz.getMethod("doSomething");
if (method.isAnnotationPresent(Log.class)) {
    Log log = method.getAnnotation(Log.class);
    // 处理逻辑
}

@Retention 三种策略:

  • SOURCE:编译后丢弃(如 @Override
  • CLASS:保留在 .class 但运行时不可见
  • RUNTIME:运行时可通过反射读取

相关链接