Java 反射(Reflection)(超详细)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

1. 什么是 Java 反射(Reflection)?

Java 反射(Reflection)是一种让程序在运行时能够动态获取自身结构、操作对象属性或调用方法的技术。它就像是给程序装上了一面“X光镜”,开发者可以通过这面镜子看到程序内部的类、方法、字段等细节,并在运行时根据需要进行灵活调整。

形象比喻
想象你正在拆解一台复杂的机器,而反射就是一把“万能螺丝刀”。无论这台机器有多少隐藏的零件,这把螺丝刀都能帮你找到并操作它们,即使这些零件在设计时并未被直接暴露出来。

2. 为什么需要反射?

2.1 动态性与灵活性

在编译时无法确定的场景下,反射能帮助程序在运行时动态加载类、创建对象或调用方法。例如:

  • 框架开发:Spring 框架通过反射自动装配 Bean。
  • 插件系统:动态加载外部 JAR 包中的类。
  • 单元测试:模拟或测试私有方法。

2.2 突破封装限制

虽然 Java 的封装性(如 private 关键字)旨在保护代码结构,但反射能绕过这些限制,直接访问或修改私有成员。例如:

// 假设有一个类 Person,其私有字段 name
public class Person {
    private String name;
    // ...
}

// 通过反射修改私有字段
Field nameField = Person.class.getDeclaredField("name");
nameField.setAccessible(true); // 强制访问
nameField.set(personInstance, "New Name");

2.3 元数据查询

反射能获取类的元数据(如方法名、注解信息),这对需要依赖“类信息本身”的场景至关重要。例如:

  • 序列化框架:如 Jackson 通过反射解析对象属性与 JSON 字段的映射关系。
  • 依赖注入:通过注解 @Autowired 定位需要注入的 Bean。

3. 反射的核心概念与操作步骤

3.1 获取 Class 对象

所有反射操作都始于 Class 对象,它是类的“蓝图”。获取它的方法有三种:

  1. 通过对象的 getClass() 方法
    Object obj = new Object();
    Class<?> objClass = obj.getClass();
    
  2. 通过类名的 .class 后缀
    Class<?> stringClass = String.class;
    
  3. 通过 Class.forName() 静态方法
    Class<?> listClass = Class.forName("java.util.ArrayList");
    

3.2 动态创建对象

反射允许在运行时通过 newInstance() 方法创建对象,但需注意:

  • 构造方法必须是公共的(否则需通过 setAccessible(true) 强制访问)。
  • Java 9 后推荐使用 getDeclaredConstructor().newInstance(),以支持私有构造方法。
// 动态创建 Person 对象
Class<?> personClass = Class.forName("com.example.Person");
Constructor<?> constructor = personClass.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 允许访问私有构造方法
Object person = constructor.newInstance("John Doe");

3.3 访问字段与方法

字段操作

通过 getFields()getDeclaredFields() 可分别获取公共字段和所有字段:

Field[] publicFields = Person.class.getFields();   // 只获取 public 字段
Field[] allFields = Person.class.getDeclaredFields(); // 获取所有字段(包括 private)

方法操作

通过 getMethods()getDeclaredMethods() 获取方法:

// 获取并调用 public 方法
Method toStringMethod = Person.class.getMethod("toString");
String result = (String) toStringMethod.invoke(personInstance);

// 获取私有方法并调用
Method privateMethod = Person.class.getDeclaredMethod("privateMethod");
privateMethod.setAccessible(true);
privateMethod.invoke(personInstance);

3.4 处理注解

反射可以读取类、方法或字段上的注解信息:

// 获取类上的注解
Annotation[] annotations = Person.class.getAnnotations();

// 获取方法上的注解
Method method = Person.class.getMethod("setName", String.class);
Annotation annotation = method.getAnnotation(MyAnnotation.class);

4. 反射的典型应用场景与案例

4.1 案例 1:动态调用私有方法

假设有一个类 Calculator,其私有方法 private int multiply(int a, int b) 需要被外部调用:

public class Calculator {
    private int multiply(int a, int b) {
        return a * b;
    }
}

// 通过反射调用
Method multiplyMethod = Calculator.class.getDeclaredMethod("multiply", int.class, int.class);
multiplyMethod.setAccessible(true);
int result = (int) multiplyMethod.invoke(calculatorInstance, 5, 3); // result = 15

4.2 案例 2:实现简单工厂模式

反射可以动态创建对象,无需硬编码类名:

public class DynamicFactory {
    public static Object createInstance(String className) 
        throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> clazz = Class.forName(className);
        return clazz.newInstance();
    }
}

4.3 案例 3:解析 JSON 数据

假设需要将 JSON 字符串转换为对象,但字段名与类属性名不一致:

public class User {
    private String fullName; // JSON 中的字段名为 "full_name"
    // ...
}

// 通过反射设置字段值
Field field = User.class.getDeclaredField("fullName");
field.setAccessible(true);
field.set(userInstance, jsonObject.getString("full_name"));

5. 反射的局限性与性能问题

5.1 安全性风险

反射可能破坏封装性,例如绕过 private 权限修改敏感数据,需谨慎使用。

5.2 性能开销

反射操作涉及动态查找方法、检查权限等,比直接调用慢 10~100 倍。建议:

  • 缓存 Class 对象或 Method 对象
    private static final Method METHOD = getMethodOnce();
    
  • 避免在循环中使用反射
    // ❌ 不推荐
    for (Object obj : list) {
        method.invoke(obj); // 每次调用反射
    }
    
    // ✅ 优化后
    Method cachedMethod = ... // 提前缓存
    for (Object obj : list) {
        cachedMethod.invoke(obj); // 直接使用缓存
    }
    

6. 反射与动态代理

Java 的动态代理(Dynamic Proxy)是反射的延伸应用,允许在运行时为接口创建代理对象。例如:

// 定义接口
interface Service {
    void execute();
}

// 创建代理对象
Service proxy = (Service) Proxy.newProxyInstance(
    Service.class.getClassLoader(),
    new Class<?>[]{Service.class},
    (proxyObj, method, args) -> {
        System.out.println("Before method");
        method.invoke(realService, args);
        System.out.println("After method");
        return null;
    }
);

7. 总结:合理使用反射的黄金法则

反射是一把“双刃剑”,需遵循以下原则:

  1. 优先使用常规方法:除非动态性需求不可替代。
  2. 避免过度暴露内部细节:合理设置类的可见性。
  3. 性能优化第一:缓存反射对象并减少高频调用。
  4. 结合注解与框架:如使用 Lombok 的 @NoArgsConstructor 替代手动反射构造对象。

通过反射,开发者能突破静态编译的限制,但需始终以代码的可维护性和效率为最高目标。


关键词布局检查

  • 文章中通过“Java 反射”“反射”“动态操作”等自然融入核心关键词,满足 SEO 要求。
  • 案例与代码示例覆盖了反射的核心功能,确保内容实用性。

最新发布