Java中的黑盒魔法:反射

一、概述

反射是一种在运行时动态获取和操作类的信息的能力。它允许程序在编译时不需要明确知道类的具体类型,而是在运行时通过获取类的元数据来进行操作。

Java提供了强大的反射API,它使得我们可以在程序运行时获取类的信息,并进行动态的方法调用、字段访问和对象创建等操作。

二、反射的执行流程是怎么样的

sequenceDiagram participant 应用程序 participant 反射API participant 类(Class)对象 应用程序 ->> 反射API: 调用反射API 反射API ->> 类(Class)对象: 获取类信息 类(Class)对象 ->> 反射API: 返回类信息 反射API ->> 应用程序: 返回类信息 反射API ->> 类(Class)对象: 创建对象 类(Class)对象 ->> 反射API: 返回对象 反射API ->> 应用程序: 返回对象 反射API ->> 类(Class)对象: 调用方法 类(Class)对象 ->> 反射API: 返回方法结果 反射API ->> 应用程序: 返回方法结果

在上述泳道图中,有三个泳道(swimlane)代表不同的参与者或角色。应用程序是使用反射的主体,它调用反射API来实现反射操作。反射API充当中间层,它与应用程序进行交互,并与类对象进行通信。

泳道图展示了以下流程:

  1. 应用程序调用反射API进行反射操作。

  2. 反射API通过获取类对象来获取类信息。

  3. 类对象提供类信息给反射API。

  4. 反射API将类信息返回给应用程序。

  5. 反射API使用类对象来创建对象。

  6. 类对象返回创建的对象给反射API。

  7. 反射API将创建的对象返回给应用程序。

  8. 反射API使用类对象来调用方法。

  9. 类对象返回方法执行结果给反射API。

  10. 反射API将方法执行结果返回给应用程序。

2.1 Java 获取反射三种方法

graph LR A(Java 获取反射方法) B(使用 Class.forName方法) C(使用 .class 语法) D(使用对象的 getClass 方法) A ---> B A ---> C A ---> D style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px
  • 使用 Class.forName() 方法

通过类的完全限定名(包括包名)来获取类的反射信息。例如,要获取名为 MyClass 的类的反射信息,可以使用以下代码:

java 复制代码
Class<?> myClass = Class.forName("com.example.MyClass");
  • 使用 .class 语法

使用类字面常量(class literal)来获取类的反射信息。例如,要获取名为 MyClass 的类的反射信息,可以使用以下代码:

java 复制代码
Class<?> myClass = MyClass.class;
  • 使用对象的 getClass() 方法

如果已经有类的实例对象,可以使用该实例对象的 getClass() 方法获取类的反射信息。例如,要获取对象 myObject 的类的反射信息,可以使用以下代码:

java 复制代码
Class<?> myClass = myObject.getClass();

三、反射可以实现什么功能

通过反射,我们可以实现以下功能:

graph LR A(反射可以做什么) B(获取类的信息) C(创建对象) D(调用方法) E(访问和修改字段) A ---> B A ---> C A ---> D A ---> E style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px
  • 获取类的信息:通过Class对象可以获取类的名称、修饰符、父类、接口、构造方法、方法和字段等信息。这使得我们可以在运行时了解类的结构和特征。

  • 创建对象:通过反射,可以根据类的信息动态地创建对象。通过获取类的构造方法对象,我们可以实例化类的对象,即使在编译时并不知道类的具体类型。

  • 调用方法:通过反射,可以动态地调用类的方法。通过获取类的方法对象,我们可以调用公有方法、私有方法和静态方法,并传递相应的参数。

  • 访问和修改字段:通过反射,可以获取和修改类的字段的值。通过获取类的字段对象,我们可以访问和修改公有字段和私有字段的值。

四、一个案例带你实操一下反射

需求:现在假如有一个学生类,学生类有姓名和年龄字段,然后有一个学习的方法。我现在通过反射来给他设置值,并让他学习,来看看怎么实现

  • 先用一个流程图梳理一下实现的思路
graph LR O[定义学生对象] --> A[获取学生类的Class对象] --> B[创建学生对象实例] B --> C[获取学生类的属性并设置属性值] C --> D[调用学生类的方法进行行为处理]
  • 首先定义一个学生类
java 复制代码
public class Student {

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;

    public Student() {
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public void study() {
        System.out.println(age + " 岁的 " + name + " 正在学习中...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
  • 下面来看看通过反射怎么实现让一个学生学习
java 复制代码
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) {
        try {
            // 获取学生类的Class对象
            Class<Student> studentClass = Student.class;
            // 创建学生对象实例
            Student student = studentClass.newInstance();

            // 获取学生类的属性并设置属性值
            Field nameField = studentClass.getDeclaredField("name");
            nameField.setAccessible(true); // 设置私有属性可访问
            nameField.set(student, "李雪"); // 设置name属性值为"李雪"

            Field ageField = studentClass.getDeclaredField("age");
            ageField.setAccessible(true);
            ageField.set(student, 18); // 设置age属性值为 18

            // 调用学生类的方法进行相应行为处理
            Method studyMethod = studentClass.getDeclaredMethod("study");
            studyMethod.setAccessible(true);
            studyMethod.invoke(student); // 调用study方法进行学习行为
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

}
  • 输出结果
java 复制代码
18 岁的 李雪 正在学习中...

在上述示例代码中,假设 Student 类有 nameage 两个属性,并且有一个 study 方法用于学习行为。

通过反射,我们首先获取了 Student 类的 Class 对象,然后使用该对象创建了一个学生对象实例。

接下来,使用反射获取学生类的属性,并设置相应的属性值。

最后,通过反射获取学生类的方法,并调用该方法进行相应的行为处理。

需要注意的是,上述代码假设 nameage 属性均为私有属性,需要通过 setAccessible(true) 设置为可访问,以便通过反射进行修改。

另外如果属性有参数的构造方法,需要使用 getDeclaredConstructor() 获取构造方法对象,并调用 newInstance() 创建对象时传递相应的参数。

此外,还需要处理反射相关的异常,如 NoSuchFieldExceptionIllegalAccessExceptionNoSuchMethodExceptionInvocationTargetException 等。本示例没有对这些异常进行处理

五、反射都有哪些应用场景

graph LR A(反射应用场景) B(框架和库) C(序列化和反序列化) D(动态代理) E(许多框架和库使用反射来实现灵活的配置和扩展能力) E1(spring) E2(hibernate) F(反射允许在序列化和反序列化过程中动态地操作对象的属性) G(反射使得可以在运行时动态地生成代理对象,并拦截对真实对象的方法调用) G1(mybatis) A --> B --> E E --> E1 E --> E2 A --> C --> F A --> D --> G --> G1 style B fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style C fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style D fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style E1 fill:#FFC0CB,stroke:#FFC0CB,stroke-width:2px style E2 fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style F fill:#ADD8E6,stroke:#ADD8E6,stroke-width:2px style G fill:#00FFFF,stroke:#00FFFF,stroke-width:2px style G1 fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px

六、总结

反射是一项强大但需要谨慎使用的技术,反射几乎可以是各种框架的基础,然而,反射也有一些潜在的问题和注意事项,它可以动态地操作类和对象,但可能导致性能开销、安全风险和代码可读性的问题。

因此,反射应该谨慎使用,并在必要时使用。在普通的业务逻辑中,应该优先考虑使用正常的静态类型和编译时检查,以提高性能和可维护性。

希望本文能给你带来帮助,如有错误或建议,欢迎指正和提出。

相关推荐
所待.3831 分钟前
小小扑克牌算法
java·算法
.生产的驴9 分钟前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq
.生产的驴9 分钟前
SpringBoot 消息队列RabbitMQ在代码中声明 交换机 与 队列使用注解创建
java·spring boot·分布式·servlet·kafka·rabbitmq·java-rabbitmq
海里真的有鱼17 分钟前
Spring Boot 中整合 Kafka
后端
idealzouhu23 分钟前
Java 并发编程 —— AQS 抽象队列同步器
java·开发语言
布瑞泽的童话23 分钟前
无需切换平台?TuneFree如何搜罗所有你爱的音乐
前端·vue.js·后端·开源
听封27 分钟前
Thymeleaf 的创建
java·spring boot·spring·maven
写bug写bug33 分钟前
6 种服务限流的实现方式
java·后端·微服务
离开地球表面_9943 分钟前
索引失效?查询结果不正确?原来都是隐式转换惹的祸
数据库·后端·mysql