反射
→ 返回 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
| 方法 | 范围 | 访问权限 |
|---|---|---|
getMethod | public 方法(含父类) | 只有 public |
getDeclaredMethod | 本类所有方法 | 任意(需 setAccessible) |
getField | public 字段(含父类) | 只有 public |
getDeclaredField | 本类所有字段 | 任意(需 setAccessible) |
反射与泛型
泛型在编译后会类型擦除,运行时无法直接通过反射获取泛型类型参数。但可以通过 ParameterizedType 获取声明处的泛型:
// 获取父类的泛型参数
Type superClass = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) superClass;
Type[] args = pt.getActualTypeArguments(); // [String.class]Spring 的
ResolvableType、Jackson 的TypeReference都是利用这个原理。
性能问题与优化
反射比直接调用慢,主要开销:
- 方法查找(字符串匹配)
- 参数装箱/拆箱
- 安全检查
优化手段:
- 缓存
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:运行时可通过反射读取
相关链接
- IOC与DI — 反射的框架运用