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 参数)。


六、反射的性能问题

为什么反射慢?

  1. 解释执行:JVM 对反射调用无法有效优化(如内联);
  2. 安全检查:每次调用都要做访问权限校验(除非 setAccessible(true));
  3. 装箱/拆箱:参数和返回值都是 Object,涉及自动装箱;
  4. 方法查找开销:需在运行时解析方法签名。

性能对比(粗略):

方式 相对速度
直接调用 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 对象是反射的起点
三大组件 ConstructorMethodField
访问控制 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)
posted @ 2025-12-26 11:44  火星程序员ty  阅读(3)  评论(0)    收藏  举报