Java核心知识体系5:反射机制详解

Java核心知识体系1:泛型机制详解
Java核心知识体系2:注解机制详解
Java核心知识体系3:异常机制详解
Java核心知识体系4:AOP原理和切面应用

1 介绍

无论是那种语言体系,反射都是必不可少的一个技术特征。从Java体系来说,很多常用的技术框架或多或少都使用到了反射技术,比如Spring、MyBatis、RocketMQ、FastJson 等等。反射技术强大而必要,在大多数框架中起到举足轻重的作用。所以,反射也是Java必不可少的核心技术之一。

接下来我们来看看反射的一些技术要点:

  1. 反射的概念(即什么是反射)?
  2. 反射的作用(它帮我们解决了哪些问题)?
  3. 反射的实现原理?
  4. 如何使用反射?
    下面我就针对以上的疑问,一一来讲解。

1.1 反射是什么?

Java反射(Reflection)是Java语言的一个核心特性,它允许运行中的Java代码对自身进行自我检查,甚至修改自身的组件。具体来说,反射机制提供了在运行状态中,对于任意一个类,都能够了解这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法在Java中就叫做反射。

一句话总结:反射就是在运行时才具体知晓要操作的类是什么结构,并在运行时获取类的完整构造,并调用对应的方法、属性等。

Java的反射主要包括以下三个部分:

  • 类的加载:Java的类在需要使用时才会被加载到JVM中。这个过程是由类加载器(ClassLoader)完成的。类加载器首先检查这个类是否已经被加载过,如果还没有加载,那么就会从磁盘上加载类的字节码并创建一个Class对象。
  • 获取类的信息:当一个对象被创建后,我们可以使用反射来获取这个对象的Class对象。通过这个Class对象,我们可以获取到这个类的所有属性和方法。
  • 方法的调用:通过反射,我们可以动态的调用一个对象的方法。即使这个方法是一个私有的方法,也能够通过反射来调用。

1.2 为什么要用反射?

Java Reflection功能非常强大,并且非常有用,比如:

  • 获取任意类的名称、package信息、所有属性、方法、注解、类型、类加载器等
  • 获取任意对象的属性,并且能改变对象的属性
  • 调用任意对象的方法
  • 判断任意一个对象所属的类
  • 实例化任意一个类的对象
  • 通过反射我们可以实现动态装配,降低代码的耦合度,实现动态代理等。

具体的应用场景:

  • 框架设计:许多框架,如Spring,Hibernate等,都大量使用了反射来实现对象的自动装配,动态代理等功能。
  • 单元测试:单元测试框架(如JUnit)会使用反射来调用被注解的方法。
  • 插件化:为了实现插件化,可以通过反射加载不同的插件。
  • 对象序列化与反序列化:在对象进行序列化和反序列化的时候,会使用反射获取到对象的所有属性和方法。

2 反射的使用

在Java中,Class类与java.lang.reflect类库配合对反射技术进行了完整的支持。在反射的Package中,我们经常使用功能类如下:

  • Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象
  • Field类表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)
  • Method类表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)

下面将对这几个类进行详细介绍。

2.1 反射创建类对象

一般情况下我们通过反射创建类对象主要有两种方式:

  • 通过 Class 对象的 newInstance() 方法

  • 通过 Constructor 对象的 newInstance() 方法

  • 通过 Class 对象的 newInstance() 方法实现

    Class clz = Class.forName("com.ad.reflection.TestRefle");
    TestRefle tr= (TestRefle)clz.newInstance();

  • 通过 Constructor 对象的 newInstance() 方法实现

    Class clz = Class.forName("com.ad.reflection.TestRefle");
    Constructor constructor = clz.getConstructor();
    TestRefle tr= (TestRefle)constructor.newInstance();

这边需要注意,通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。

下面的代码演示的是通过 Constructor 调用有参构造方法进行了类对象初始化:

复制代码
Class clz = Class.forName("com.ad.reflection.TestRefle");
Constructor constructor = clz.getConstructor(String.class);
TestRefle tr= (TestRefle)constructor.newInstance("提供一个String参数");

接下来我们继续,通过具体的API获取详细的类信息:类信息、方法信息、属性信息等。

2.2 获取Class类对象

复制代码
 // 获取Class对象的三种方式
 根据类名: Class mailClass = MailInfo.class;
 根据对象: Class mailClass = new MailInfo().getClass();
 根据全限定类名: Class mailClass = Class.forName("com.ad.MailInfo");
 
 // 根据对象获取信息和实例对象
 获取全限定类名: mailClass.getName();
 获取类名: mailClass.getSimpleName();
 实例化: userClass.getDeclaredConstructor().newInstance();

更加详细Class类获取参考如下:

方法 用途
forName() (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 (2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass() 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName() 取全限定的类名(包括包名),即类的完整名字。 getSimpleName() 获取类名(不包括包名)
getCanonicalName() 获取全限定的类名(包括包名)
isInterface() 判断Class对象是否是表示一个接口
getInterfaces() 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss() 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance() 返回一个Oject对象,是实现"虚拟构造器"的一种途径。使用该方法创建的类,必须带有无参的构造器。

2.3 获取类的成员变量的信息

复制代码
Field[] fields = _class.getDeclaredFields();

更加详细成员变量获取参考如下:

方法 用途
getField(String name) 获得某个公有的属性对象
getFields() 获取所有的公有的属性对象
getDeclaredField(String name) 获得某个属性对象(public和非public)
getDeclaredFields() 获得所有属性对象(public和非public)

2.4 获得类方法

复制代码
Method[] methods = _class.getDeclaredMethods();

更加详细方法获取参考如下:

方法 用途
getMethod(String name, Class...<?> paramerterTypes) 获得某个公有的方法对象
getMethods() 获取所有的公有的方法对象
getDeclaredMethod(String name, Class...<?> paramerterTypes) 获得对应类下某个方法(public和非public)
getDeclaredMethods() 获得对应类下所有方法(public和非public)

2.5 获得构造函数

复制代码
Constructor[] constructors = _class.getDeclaredConstructors();

更加详细构造函数获取参考如下:

方法 用途
getConstructor(Class...<?> paramerterTypes) 获得该类中与参数类型匹配的公有构造方法
getConstructors() 获取该类的所有公有构造方法
getDeclaredConstructor(Class...<?> paramerterTypes) 获得该类中与参数类型匹配的构造方法
getDeclaredConstructors() 获取该类的所有构造方法

这样通过反射就可以做在运行时获取类的完整构造,并获得类信息了。

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(即类的属性)
Method类 代表类的方法
Constructor类 代表类的构造函数

通过上面的几个示例我们基本了解了反射的使用,但这仅仅是使用,我们还需深入理解反射背后的底层实现原理。

3 反射原理分析

3.1 反射的调用流程

1、编写完Java项目之后,java文件都会被编译成一个.class文件

2、这些class文件在程序运行时会被ClassLoader加载到JVM中,当一个类被加载以后,JVM就会在内存中自动产生一个Class对象。

3、通过Class对象获取 Field(属性)、Method(方法)、Construcor(构造函数)

我们平时通过new的形式创建对象,本质上就是通过Class来创建个新对象

通过上面的流程我们可以看出反射的优势:

  • 动态装配

我们的程序在运行时,可能不一定会用到所有我们编写和构建的类,这样避免启动时间太长并且浪费大量无用的机器资源。

取而代之的是动态的加载一些类,这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。

  • 降低耦合
    如果你在使用new时明确的指定类名,那这就是典型的硬编码实现,而在使用反射的时候,可以只传入类名参数,就可以生成对象,降低了耦合度,使得程序更具灵性。

完整的调用流程,图片来自网上,比较模糊,后续再补一个

3.2 反射的应用场景

  • 框架设计:许多框架,如Spring,Hibernate,mybatis,dubbo,rocketmq等,都大量使用了反射来实现对象的自动装配,动态代理等功能。
  • 单元测试:单元测试框架(如JUnit)会使用反射来调用被注解的方法。
  • 插件化:为了实现插件化,可以通过反射加载不同的插件。
  • 对象序列化与反序列化:在对象进行序列化和反序列化的时候,会使用反射获取到对象的所有属性和方法。
  • 动态配置、动态代理:通过反射去读取配置,以及代理请求

4 反射经典案例解析

以下案例来自百度文心一言大模型自动生成,已调试通过。

复制代码
import java.lang.reflect.Method;  
  
public class ReflectionExample {  
    public static void main(String[] args) {  
        try {  
            // 获取目标类的Class对象  
            Class<?> targetClass = Class.forName("java.util.ArrayList");  
  
            // 获取目标类的所有公共方法  
            Method[] methods = targetClass.getMethods();  
  
            // 遍历所有方法并打印方法名  
            for (Method method : methods) {  
                System.out.println(method.getName());  
            }  
  
            // 获取特定方法,比如添加元素的add方法  
            Method addMethod = targetClass.getMethod("add", Object.class);  
  
            // 创建目标类的实例对象  
            Object targetObject = targetClass.newInstance();  
  
            // 调用add方法添加元素  
            addMethod.invoke(targetObject, "Hello, World!");  
  
            // 获取目标类的所有属性(字段)并打印属性名  
            Field[] fields = targetClass.getDeclaredFields();  
            for (Field field : fields) {  
                System.out.println(field.getName());  
            }  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

这个案例展示了如何使用反射来获取目标类的Class对象,获取并打印目标类的所有公共方法,获取特定方法,创建目标类的实例对象,调用目标类的方法,以及获取并打印目标类的所有属性(字段)。

总结

无论是那种语言体系(C#、Java等等),反射都是必不可少的一个技术特征。而从Java体系来说,很多常用的技术框架或多或少都使用到了反射技术,比如Spring、MyBatis、RocketMQ、FastJson 等等。

学习好Java 反射技术能帮助你更好的理解底层调用的原理,也有助于设计更加 轻巧、高内聚、低耦合 的业务框架。

相关推荐
张张张31211 分钟前
4.2学习总结 Java:list系列集合
java·学习
KATA~14 分钟前
解决MyBatis-Plus枚举映射错误:No enum constant问题
java·数据库·mybatis
xyliiiiiL29 分钟前
一文总结常见项目排查
java·服务器·数据库
shaoing31 分钟前
MySQL 错误 报错:Table ‘performance_schema.session_variables’ Doesn’t Exist
java·开发语言·数据库
腥臭腐朽的日子熠熠生辉1 小时前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian1 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之1 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码2 小时前
Spring Task 定时任务
java·前端·spring
俏布斯2 小时前
算法日常记录
java·算法·leetcode
27669582922 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿