Java 反射(Reflection)详解文档

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 clazz1 = String.class;

// 2. 通过对象的 getClass() 方法

String str = "hello";

Class 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 clazz = Person.class;

// 获取 public 构造器

Constructor cons1 = clazz.getConstructor(String.class, int.class);

// 获取所有构造器(包括 private)

Constructor[] allCons = clazz.getDeclaredConstructors();

// 创建对象(即使构造器是 private)

Constructor privateCons = clazz.getDeclaredConstructor();

privateCons.setAccessible(true); // 突破访问限制

Person p = privateCons.newInstance();

🔒 getConstructor() 只返回 public 构造器;

🔓 getDeclaredConstructor() 返回 所有声明的 构造器(含 private)。

2. 获取并调用方法(Method)

Class 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 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)

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)

相关文章

将116厘米转换为英寸
bas365

将116厘米转换为英寸

🕒 07-11 👁️ 199
一包烟将涨价近3倍,有人会戒烟吗?
bas365

一包烟将涨价近3倍,有人会戒烟吗?

🕒 10-06 👁️ 7311
流量套餐每个月什么时候更新?2026最新规则整理
beat365体育ios版下载

流量套餐每个月什么时候更新?2026最新规则整理

🕒 06-21 👁️ 4732
光伏设计软件:鹧鸪云、SolarStation与Archeios的高效解决方案
林逸有几个老婆,分别是谁?
国内365bet登录网址

林逸有几个老婆,分别是谁?

🕒 02-17 👁️ 6147
魅蓝Note5和魅蓝Note3哪个值得买?魅蓝Note5和魅蓝Note3性能对比评测
如何正确清洗和消毒蒸脸器?视频教程指南
国内365bet登录网址

如何正确清洗和消毒蒸脸器?视频教程指南

🕒 07-28 👁️ 2607
【小小世界:原野求生】小动物驯服指南
国内365bet登录网址

【小小世界:原野求生】小动物驯服指南

🕒 10-19 👁️ 7742
华山每年掉下去多少人(华山每年掉下去多少人统计)
国内365bet登录网址

华山每年掉下去多少人(华山每年掉下去多少人统计)

🕒 11-15 👁️ 1387