Appearance
反射
为什么需要反射?
反射是 Java 在运行时动态获取类的信息、操作类的属性和方法的能力。正常写代码是「类 -> 对象 -> 调用方法」,反射是反过来:「全限定类名 -> 拿到 Class -> 拿到方法/字段 -> 调用」。
框架和中间件大量依赖反射,因为框架不知道你会写什么类,只能通过配置(全限定类名)在运行时动态加载:
- Spring —
@Autowired注入依赖、@TransactionalAOP 代理 - MyBatis — Mapper 接口动态代理
- JDK 动态代理 —
Proxy.newProxyInstance()底层就是反射 - JUnit — 扫描
@Test方法并调用 - 序列化 — Jackson、Fastjson 通过反射读写字段
反射的核心:Class 对象
什么是 Class 对象?
JVM 每加载一个类,就会在方法区(元空间)生成一个对应的 java.lang.Class 对象。这个 Class 对象包含了该类的完整元信息:构造器、字段、方法、注解、父类、接口等。反射的所有操作都是通过这个 Class 对象完成的。
获取 Class 对象的三种方式
java
// 方式1:Class.forName() —— 最常用,适合配置文件中读取类名
Class<?> clazz1 = Class.forName("java.lang.String");
// 方式2:类名.class —— 编译期已知类名时使用
Class<String> clazz2 = String.class;
// 方式3:对象.getClass() —— 已有对象实例时使用
String str = "hello";
Class<? extends String> clazz3 = str.getClass();
// 基本类型的 Class
Class<Integer> intClass = int.class; // int
Class<Integer> wrapClass = Integer.TYPE; // int(等价于 int.class)
Class<Integer> wrapperClass = Integer.class; // java.lang.Integer注意:int.class 和 Integer.class 是两个不同的 Class 对象,前者代表基本类型,后者代表包装类。
核心 API
获取构造方法并创建实例
java
Class<?> clazz = Class.forName("com.example.User");
// 获取所有 public 构造器
Constructor<?>[] constructors = clazz.getConstructors();
// 获取指定参数的 public 构造器
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
// 获取所有构造器(包括 private)
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
// 创建实例
User user = (User) constructor.newInstance("张三", 25);
// 调用 private 构造器
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);
privateConstructor.setAccessible(true);
User user2 = (User) privateConstructor.newInstance("李四");获取字段并读写
java
Class<?> clazz = User.class;
User user = new User("张三", 25);
// 获取 public 字段(包括继承的)
Field[] fields = clazz.getFields();
// 获取指定名称的 public 字段
Field nameField = clazz.getField("name");
String name = (String) nameField.get(user);
nameField.set(user, "李四");
// 获取本类声明的所有字段(包括 private,不包括继承的)
Field[] declaredFields = clazz.getDeclaredFields();
// 获取并操作 private 字段
Field ageField = clazz.getDeclaredField("age");
ageField.setAccessible(true); // 关键:跳过访问检查
int age = ageField.getInt(user); // 使用类型特化方法,避免装箱
ageField.setInt(user, 30);
// 获取 static 字段(对象参数传 null)
Field staticField = clazz.getDeclaredField("DEFAULT_NAME");
staticField.setAccessible(true);
String defaultValue = (String) staticField.get(null);获取方法并调用
java
Class<?> clazz = User.class;
User user = new User("张三", 25);
// 获取所有 public 方法(包括继承的,包括 Object 的方法)
Method[] methods = clazz.getMethods();
// 获取指定签名的方法
Method sayHello = clazz.getMethod("sayHello", String.class);
Object result = sayHello.invoke(user, "World");
// 获取本类声明的所有方法(包括 private,不包括继承的)
Method[] declaredMethods = clazz.getDeclaredMethods();
// 调用 private 方法
Method privateMethod = clazz.getDeclaredMethod("secretMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(user);
// 调用 static 方法
Method staticMethod = clazz.getDeclaredMethod("factoryMethod", String.class);
staticMethod.setAccessible(true);
Object obj = staticMethod.invoke(null, "param");获取注解
java
// 获取类上的注解
if (clazz.isAnnotationPresent(Entity.class)) {
Entity entity = clazz.getAnnotation(Entity.class);
String tableName = entity.tableName();
}
// 获取字段上的注解
Field field = clazz.getDeclaredField("name");
Column column = field.getAnnotation(Column.class);
String columnName = column.name();
// 获取方法上的注解
Method method = clazz.getDeclaredMethod("save");
Annotation[] annotations = method.getAnnotations();
// 获取方法参数上的注解
Annotation[][] paramAnnotations = method.getParameterAnnotations();获取其他元信息
java
// 获取类名
String className = clazz.getName(); // com.example.User
String simpleName = clazz.getSimpleName(); // User
// 获取父类和接口
Class<?> superClass = clazz.getSuperclass();
Class<?>[] interfaces = clazz.getInterfaces();
// 获取修饰符
int modifiers = clazz.getModifiers();
boolean isPublic = Modifier.isPublic(modifiers);
boolean isAbstract = Modifier.isAbstract(modifiers);
// 获取包名
Package pkg = clazz.getPackage();setAccessible(true) 做了什么?
setAccessible(true) 告诉 JVM 跳过访问权限检查。它不修改字段的 private 修饰符,只是关闭 AccessibleObject 的访问控制。
java
Field nameField = User.class.getDeclaredField("name");
// nameField.setAccessible(true); // 注释掉这一行
nameField.set(user, "hack"); // 抛 IllegalAccessException安全影响
从 Java 9+ 开始,模块系统引入了更严格的访问控制。如果目标类在另一个模块中且该模块没有 opens 其包,反射会失败并抛出 InaccessibleObjectException。JVM 添加 --add-opens 参数可以绕过:
--add-opens java.base/java.lang=ALL-UNNAMED修改 final 字段
普通 final 字段可以通过反射修改,但 static final 的基本类型和字符串(编译期常量)不行,因为会被内联优化:
java
// 修改普通 final 字段
Field finalField = clazz.getDeclaredField("name");
finalField.setAccessible(true);
// 需要先移除 final 修饰符
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(finalField, finalField.getModifiers() & ~Modifier.FINAL);
finalField.set(obj, "new value");反射的性能
反射比直接调用慢,原因有三:
- 方法查找 —
getDeclaredMethod()要遍历方法表,时间复杂度 O(n) - 参数装箱 —
invoke()的参数是Object[],基本类型要装箱拆箱 - 安全检查 — 每次调用都要检查访问权限
性能对比
| 调用方式 | 相对耗时 | 说明 |
|---|---|---|
| 直接调用 | 1x | 基线 |
| 缓存 Method + setAccessible | ~2-3x | 实际业务中常用 |
| 不缓存 Method | ~10x+ | 每次 getMethod |
| 不 setAccessible | ~20x+ | 每次检查权限 |
优化手段
java
// 1. 缓存 Method 对象,避免重复查找
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
// 2. 务必 setAccessible(true)
method.setAccessible(true);
// 3. JDK 7+ 用 MethodHandle(介于反射和直接调用之间)
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle = lookup.findVirtual(User.class, "sayHello",
MethodType.methodType(void.class, String.class));
handle.invoke(user, "World");
// 4. JDK 8+ 用 LambdaMetafactory(接近直接调用速度)
// 适用于需要高频率调用的场景反射的实际应用场景
1. Spring 依赖注入
java
// 简化版 @Autowired 实现原理
public class SimpleContainer {
public static void autowire(Object bean) throws Exception {
for (Field field : bean.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
Object dependency = getBean(field.getType()); // 从容器获取
field.set(bean, dependency);
}
}
}
}2. ORM 结果映射
java
// 简化版 MyBatis 结果集映射
public static <T> List<T> mapResultSet(ResultSet rs, Class<T> clazz) throws Exception {
List<T> result = new ArrayList<>();
while (rs.next()) {
T instance = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
String columnName = field.getName(); // 简化:字段名=列名
Object value = rs.getObject(columnName);
field.set(instance, value);
}
result.add(instance);
}
return result;
}3. JUnit 测试框架
java
// 简化版 JUnit Runner
public static void runTests(Class<?> testClass) throws Exception {
Object testInstance = testClass.getDeclaredConstructor().newInstance();
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
try {
method.invoke(testInstance);
System.out.println(method.getName() + " PASSED");
} catch (InvocationTargetException e) {
System.out.println(method.getName() + " FAILED: " + e.getCause());
}
}
}
}反射与动态代理
JDK 动态代理的 InvocationHandler.invoke() 签名:
java
public Object invoke(Object proxy, Method method, Object[] args) throws ThrowableMethod 就是反射的核心类,method.invoke(target, args) 就是反射调用。所以:
动态代理 = 反射 + 动态字节码生成
代理类 $Proxy0 是动态生成的字节码,但真正执行目标方法时,靠的是 Method.invoke() 反射机制。
常见面试题
Q1: Class.forName() 和 ClassLoader.loadClass() 的区别?
Class.forName() 会执行类的静态初始化(static 块),ClassLoader.loadClass() 不会。JDBC 驱动加载就是靠 Class.forName() 触发静态代码块注册驱动。
java
// 会触发 static 块
Class.forName("com.mysql.cj.jdbc.Driver");
// 不会触发 static 块
ClassLoader.getSystemClassLoader().loadClass("com.mysql.cj.jdbc.Driver");Q2: getDeclaredMethods() 和 getMethods() 的区别?
getDeclaredMethods():返回本类声明的所有方法(包括 private),不包括继承的方法getMethods():返回所有 public 方法(包括继承的,包括 Object 类的方法)
Q3: getDeclaredField() 和 getField() 的区别?
同理,getDeclaredField() 获取本类任意访问权限的字段,getField() 只能获取 public 字段(包括继承的)。
Q4: 反射能修改 final 字段吗?
普通 final 字段可以,static final 的基本类型和字符串(编译期常量)不行,因为会被内联优化。需要同时移除 final 修饰符。
Q5: 反射有什么缺点?
- 性能开销大,比直接调用慢数倍到数十倍
- 破坏封装性,绕过编译期类型检查
- 代码可读性差,维护困难
- Java 9+ 模块系统进一步限制反射访问
Q6: int.class 和 Integer.class 是同一个对象吗?
不是。int.class 代表基本类型 int,Integer.class 代表包装类 Integer。int.class == Integer.TYPE 为 true,但 int.class == Integer.class 为 false。
Q7: 反射调用方法时,invoke 的第一个参数为 null 表示什么?
表示调用的是 static 方法。静态方法不依赖实例,所以第一个参数(目标对象)可以为 null。
Q8: 如何通过反射实例化一个内部类?
非静态内部类需要外部类实例作为第一个构造参数:
java
Class<?> innerClass = Class.forName("com.example.Outer$Inner");
Constructor<?> ctor = innerClass.getDeclaredConstructor(Outer.class);
Object inner = ctor.newInstance(new Outer());