📘 Java 反射(Reflection)详解文档
目标:深入理解 Java 反射机制,掌握其核心类、使用方式、运行原理及实际应用。
一、什么是反射(Reflection)?
反射是 Java 提供的一种在运行时动态获取类信息并操作对象的能力。
它允许程序在运行期间:
获取任意类的 Class 对象;
查看类的构造器、方法、字段、注解等元数据;
动态创建对象、调用方法、访问/修改字段值;
突破访问控制(如访问私有成员)。
✅ 关键点:反射操作的是“类型信息”,而不是具体的代码逻辑。它是“关于代码的代码”(元编程)。
二、为什么需要反射?
典型应用场景:
场景
说明
框架开发
Spring(IoC/DI)、Hibernate(ORM)、JUnit(测试方法发现)等都重度依赖反射
通用工具类
如 JSON 序列化(Jackson/Gson)、Bean 拷贝(BeanUtils)
插件系统
动态加载类并调用其方法
调试/IDE 工具
IDE 的自动补全、对象探查器
💡 没有反射,现代 Java 框架几乎无法实现“解耦”和“配置驱动”。
三、反射的核心:java.lang.Class
每个类在 JVM 中都有一个唯一的 Class 对象,它代表该类的运行时类型信息。
获取 Class 对象的三种方式:
// 1. 通过类字面量(最安全、高效,推荐)
Class
// 2. 通过对象的 getClass() 方法
String str = "hello";
Class extends String> clazz2 = str.getClass();
// 3. 通过 Class.forName()(常用于动态加载)
Class> clazz3 = Class.forName("java.lang.String");
⚠️ Class.forName() 会初始化类(执行 static 块),而 .class 不会。
四、反射核心 API 详解
Java 反射主要通过以下类实现(均在 java.lang.reflect 包中):
类
作用
Class
代表类本身,是反射入口
Constructor
代表构造器
Method
代表方法
Field
代表字段(成员变量)
Modifier
解析访问修饰符(public/private/static 等)
1. 获取构造器(Constructor)
Class
// 获取 public 构造器
Constructor
// 获取所有构造器(包括 private)
Constructor>[] allCons = clazz.getDeclaredConstructors();
// 创建对象(即使构造器是 private)
Constructor
privateCons.setAccessible(true); // 突破访问限制
Person p = privateCons.newInstance();
🔒 getConstructor() 只返回 public 构造器;
🔓 getDeclaredConstructor() 返回 所有声明的 构造器(含 private)。
2. 获取并调用方法(Method)
Class
Person p = new Person("Alice", 25);
// 获取 public 方法
Method getName = clazz.getMethod("getName"); // 无参
String name = (String) getName.invoke(p); // 调用 p.getName()
// 获取 private 方法
Method secret = clazz.getDeclaredMethod("internalLogic");
secret.setAccessible(true);
secret.invoke(p);
// 获取带参数的方法
Method setName = clazz.getMethod("setName", String.class);
setName.invoke(p, "Bob");
⚠️ invoke() 返回 Object,需强转;若方法返回 void,则返回 null。
3. 获取并操作字段(Field)
Class
Person p = new Person("Charlie", 30);
// 获取 public 字段
Field ageField = clazz.getField("age"); // 注意:必须是 public
// 获取 private 字段
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true); // 关键!否则 IllegalAccessException
// 读取字段值
String name = (String) nameField.get(p);
// 修改字段值
nameField.set(p, "David");
❗ 字段必须属于对象实例(非静态字段需传对象;静态字段可传 null)。
4. 获取类的其他信息
Class> clazz = ArrayList.class;
// 获取父类
Class> parent = clazz.getSuperclass(); // AbstractList
// 获取实现的接口
Class>[] interfaces = clazz.getInterfaces(); // List, RandomAccess...
// 获取包信息
Package pkg = clazz.getPackage();
// 判断类类型
clazz.isInterface(); // false
clazz.isArray(); // false
clazz.isEnum(); // false
clazz.isPrimitive(); // false(基本类型如 int.class 才是 true)
// 获取注解(需 @Retention(RUNTIME))
MyAnnotation anno = clazz.getAnnotation(MyAnnotation.class);
五、访问控制与 setAccessible(true)
默认情况下,反射遵守 Java 访问控制规则(private 成员不可访问)。
调用 setAccessible(true) 可绕过访问检查(基于安全管理器策略)。
这是 “暴力反射” 的核心手段。
⚠️ 在模块化系统(Java 9+)中,跨模块访问私有成员可能被禁止(需 --add-opens 参数)。
六、反射的性能问题
为什么反射慢?
解释执行:JVM 对反射调用无法有效优化(如内联);
安全检查:每次调用都要做访问权限校验(除非 setAccessible(true));
装箱/拆箱:参数和返回值都是 Object,涉及自动装箱;
方法查找开销:需在运行时解析方法签名。
性能对比(粗略):
方式
相对速度
直接调用
1x(最快)
反射(未缓存 Method)
~10x 慢
反射(缓存 Method + setAccessible)
35x 慢
MethodHandle(Java 7+)
接近直接调用(但复杂)
✅ 最佳实践:缓存 Method/Field 对象,避免重复查找。
七、反射 vs 编译时 vs 运行时
特性
编译时
反射(运行时)
类型安全
✅ 强类型检查
❌ 弱类型(Object)
性能
⚡ 极快
🐢 较慢
灵活性
❌ 固定代码
✅ 动态行为
可维护性
✅ IDE 支持好
❌ 字符串易出错(如 "setName")
🎯 原则:优先使用编译时机制;仅在必要时(如框架、通用工具)使用反射。
八、安全与模块化限制(Java 9+)
从 Java 9 开始,模块系统(JPMS)默认禁止跨模块访问内部 API。
例如:
// 尝试反射访问 java.util.ArrayList 的 elementData(private)
Field f = ArrayList.class.getDeclaredField("elementData");
f.setAccessible(true); // 在 Java 17+ 可能抛 InaccessibleObjectException
解决方案:
启动时添加 JVM 参数开放模块:
--add-opens java.base/java.util=ALL-UNNAMED
📌 Spring、Hibernate 等框架在新版 Java 中需额外配置才能正常工作。
九、典型实战案例
案例 1:通用 Bean 复制工具
public static void copyProperties(Object src, Object dest) throws Exception {
Class> srcClass = src.getClass();
Class> destClass = dest.getClass();
for (Field field : srcClass.getDeclaredFields()) {
field.setAccessible(true);
Field destField = null;
try {
destField = destClass.getDeclaredField(field.getName());
destField.setAccessible(true);
destField.set(dest, field.get(src));
} catch (NoSuchFieldException e) {
// 忽略不存在的字段
}
}
}
案例 2:基于注解的权限校验(伪代码)
Method method = target.getClass().getMethod("deleteUser");
if (method.isAnnotationPresent(RequireRole.class)) {
RequireRole anno = method.getAnnotation(RequireRole.class);
if (!currentUser.hasAnyRole(anno.value())) {
throw new AccessDeniedException();
}
}
method.invoke(target);
十、反射的替代方案(现代 Java)
需求
替代方案
动态调用
MethodHandle(更高效,但复杂)
代码生成
注解处理器(APT) + 编译时生成代码(如 Lombok)
依赖注入
使用接口 + 工厂模式(避免反射)
配置绑定
使用 record + 构造器(Java 14+)
🔮 趋势:尽量减少运行时反射,转向编译时处理。
十一、总结:反射的核心要点
要点
说明
入口
Class 对象是反射的起点
三大组件
Constructor、Method、Field
访问控制
setAccessible(true) 可突破 private
性能代价
避免高频调用,缓存反射对象
安全限制
Java 9+ 模块系统限制跨模块反射
适用场景
框架、工具库、动态加载,非业务逻辑首选
附录:常用方法速查表
操作
方法
获取 public 方法
getMethod(name, params...)
获取所有方法
getDeclaredMethods()
调用方法
method.invoke(obj, args...)
获取 public 字段
getField(name)
获取所有字段
getDeclaredFields()
设置字段值
field.set(obj, value)
创建对象
constructor.newInstance(args...)
判断是否有注解
isAnnotationPresent(Anno.class)