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

六、总结

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

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

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

相关推荐
小码哥_常2 小时前
一套自己部署的 IM,客户端是同一份 Flutter
前端·后端
学长毕设4 小时前
基于SpringBoot的实验室资产管理系统
java·spring boot·后端
zzu123zsw6 小时前
从臃肿的模板调用到声明式契约:深入 UniHttp 动态代理与多渠道生命周期扩展
java·spring boot·microsoft·unihttp
源码集结号6 小时前
云诊所门诊系统源码,基于SpringBoot+Vue.js架构,支持多租户设计与Docker容器化部署
java·spring boot·源码·医院信息系统·门诊系统·诊所系统
努力的小雨7 小时前
给 AI 眼镜开发应用有多难?我从零做了一个陪练,还上架了
后端
论文复现现场7 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
啃瓜子的松鼠7 小时前
泛微OAe9-后端二开异步调用
java
秋天的怀念_David8 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
excel8 小时前
双 Token 与前端使用 Crypto 公私钥鉴权换 Token,哪种方案更好?
前端·后端
外收内放8 小时前
Python基础语法练习题(73)
java·前端·python