Java 反射(Reflection)详解文档
📘 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<String> clazz1 = String.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<T> |
代表类本身,是反射入口 |
Constructor<T> |
代表构造器 |
Method |
代表方法 |
Field |
代表字段(成员变量) |
Modifier |
解析访问修饰符(public/private/static 等) |
1. 获取构造器(Constructor)
Class<Person> clazz = Person.class;
// 获取 public 构造器
Constructor<Person> cons1 = clazz.getConstructor(String.class, int.class);
// 获取所有构造器(包括 private)
Constructor<?>[] allCons = clazz.getDeclaredConstructors();
// 创建对象(即使构造器是 private)
Constructor<Person> privateCons = clazz.getDeclaredConstructor();
privateCons.setAccessible(true); // 突破访问限制
Person p = privateCons.newInstance();
🔒
getConstructor()只返回 public 构造器;
🔓getDeclaredConstructor()返回 所有声明的 构造器(含 private)。
2. 获取并调用方法(Method)
Class<Person> clazz = Person.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> clazz = Person.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) | |
| 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) |
浙公网安备 33010602011771号