454. Java 反射 - 简介
1. 什么是 Reflection?
Reflection(反射) 允许 Java 程序在运行时(Runtime)检查或修改应用的行为。
你可以把反射想象成一面镜子:
- 对象照镜子:通过反射,一个对象可以“看到”自己拥有哪些属性(Field)、方法(Method)和构造器(Constructor)。
- 动态操作:不仅能看,你还可以通过镜子在运行时动态地改变字段值、调用方法,甚至创建新的对象实例。
2. 核心 API 操作
反射的核心在于获取类的 Class 对象,进而操作其内部结构。
2.1 获取 Class 对象
有三种常见方式获取一个类的 Class 对象:
// 1. 通过类字面量
Class<?> clazz1 = String.class;
// 2. 通过对象实例
Class<?> clazz2 = "Hello".getClass();
// 3. 通过全限定类名(常用于动态加载)
Class<?> clazz3 = Class.forName("java.lang.String");
2.2 构造器、字段与方法
以下是操作一个普通 Java 类(如 Person)的常用 API:
| 操作对象 | 核心方法 | 示例代码 |
|---|---|---|
| 构造器 (Constructor) | getConstructor() |
Constructor<Person> c = Person.class.getConstructor(); Person p = c.newInstance(); // 动态创建对象
|
| 字段 (Field) | getDeclaredFields() |
Field f = Person.class.getDeclaredField("name"); f.setAccessible(true); // 访问私有成员 f.set(p, "Alice"); // 设置值
|
| 方法 (Method) | getMethods() |
Method m = Person.class.getMethod("setAge", int.class); m.invoke(p, 25); // 动态调用方法
|
3. Reflection 的常见用途
🧩 扩展性与插件系统
反射允许根据类的全限定名动态加载用户自定义类。这在 Spring 框架(依赖注入) 和各类 插件系统 中极为常见,开发者无需在代码中硬编码具体的类名。
🔍 开发工具与 IDE
IDE(如 IntelliJ 或 Eclipse)的类浏览器、自动代码提示功能,都是通过反射枚举类的字段、方法和接口来实现的。
🐞 自动化测试与调试
-
JUnit:自动发现并执行带有
@Test注解的方法。 - 调试器:在程序运行时检查对象的私有字段状态。
4. Reflection 的优缺点(双刃剑)
反射是一把功能强大的“双刃剑”,在提供极致灵活性的同时也带来了风险。
✅ 优点
- 减少重复代码:可以编写通用的逻辑来处理不同类的字段(如通用的 ORM 框架)。
- 极高的灵活性:支持在不重新编译的情况下改变程序的行为。
- 突破封装限制:可以访问和修改通常不可见的私有成员(在特定调试场景下非常有用)。
❌ 缺点与风险
- 🐢 性能开销:反射涉及动态解析,JVM 无法对其进行深度优化,调用速度明显慢于直接调用。避免在循环百万次的高频场景下使用。
-
⚠ 内部暴露(安全性):反射通过
setAccessible(true)打破了封装性。如果类内部字段名改变,反射代码将直接崩溃,导致代码难以维护且不可移植。 - 🔒 安全限制:在受限环境(如 Java 9 模块化系统或特定安全容器)下,反射操作可能会被禁止。
5. 总结
- 什么时候用:开发框架(Spring, MyBatis)、编写工具软件、实现插件化功能、自动化测试。
- 什么时候不用:普通的业务逻辑开发。
- 核心原则:谨慎使用。如果可以通过多态或接口实现的功能,优先不要使用反射。