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 类有 name 和 age 两个属性,并且有一个 study 方法用于学习行为。

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

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

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

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

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

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

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

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

六、总结

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

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

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

相关推荐
苍何几秒前
开源微信流,微信聊天记录,可以直接给 Codex 和 Obsidan 了
后端
明月_清风1 分钟前
JEV 又有新玩法:当 AI Agent 开始拥有一个“决策层”
人工智能·后端·agent
用户3721574261351 分钟前
如何使用 Java 从 PDF 中提取图片及获取图片信息
java
沙蒿同学3 分钟前
Wails v2 实战:用 Go + Vue3 做一个真正能用的 AI 桌面应用
前端·javascript·后端
站大爷IP3 分钟前
Python的FastAPI把我坑惨了,原来async def和def的区别这么大
后端
咕白m6253 分钟前
如何在 Word 中创建可填充表单(Java 实现)
java
货拉拉技术3 分钟前
大模型在货拉拉营销广告的应用实践
后端
怕浪猫3 分钟前
AI 知识库 WeKnora(腾讯微信团队出品)
后端·面试·github
Gopher_HBo3 分钟前
zap日志 整体架构与数据流
后端
颜进强3 分钟前
09 · NestJS Middleware 中间件:链路最外层那个"最像 Express"的家伙
前端·后端·ai编程