本文主要对 Java 反射机制原理、使用方法进行了介绍。

什么是 Java 反射?

通常,java在编译之后,会将Java代码生成为class源文件,JVM启动时,将会载入所有的源文件,并将类型信息存放到方法区中;将所有对象实例存放在Java堆中,同时也会保存指向类型信息的指针。

以下分两种情况来分析,直接使用类和使用反射的区别,以此理解反射的实现原理。

直接使用类

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

1
2
Apple apple = new Apple(); 		//直接初始化,「正射」
apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为「正」。

反射

在一个方法中,如果我们不知道在**实际运行(runtime)**时,它将要处理的对象是谁,它的类型信息是怎么样的(即在一开始并不知道我要初始化的类对象是什么),那我们如何访问这个对象或为这个对象创建一个新的实例呢?

与直接使用类相反,反射在运行时,通过读取方法区中的字节码,来动态的找到其反射的类以及类的方法和属性等(实际上就是在运行时,根据全类型名在方法区找对应的类),用这些类型信息完成对该类实例的操作,其实就是直接使用类的一个逆向使用。

例如,我们使用 JDK 提供的反射 API 进行反射调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 获取类的 Class 对象实例
Class clz = Class.forName("com.chenshuyi.reflect.Apple");

// 根据 Class 对象实例获取 Constructor 对象
Constructor constructor = clz.getConstructor();

// 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object object = constructor.newInstance();

// 获取方法的 Method 对象
Method method = clz.getMethod("setPrice", int.class);
// 利用 invoke 方法调用方法
method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。

类的正常加载过程和Java反射的过程,如图:

image-20250402151013368

所以说什么是反射?

Java 反射机制是指程序在运行时才知道要操作的类是什么,并且能够在运行时动态地获取类的信息(如类名、方法、字段、构造函数等),并且可以动态地调用对象的方法或访问字段

为什么需要Java反射?

Java 是静态语言,编译时就需要确定类的结构。这种方式虽然安全,但在需要高度灵活、动态扩展、解耦合的场景下就显得束手束脚

反射机制的作用就是打破这种编译期限制,让你可以:

  • 在运行时动态加载类
  • 在运行时动态创建对象、访问字段、调用方法
  • 提供更加通用、通配、框架化的设计

实际应用场景

  1. 插件系统、热插拔架构

    插件(比如 IDEA 插件、游戏插件)通常是独立模块,不在主程序中直接引用。

    主程序运行时通过反射加载这些类:

    1
    2
    3
    4
    Class<?> pluginClass = Class.forName("com.plugin.MyPlugin");
    Object plugin = pluginClass.getDeclaredConstructor().newInstance();
    Method run = pluginClass.getMethod("run");
    run.invoke(plugin);

    这就实现了模块解耦,主程序无需编译时就知道所有插件。

  2. 序列化/反序列化框架

    Jackson、Gson 等 JSON 解析器:

    • 通过反射读取 Java 类的字段
    • 动态地将 JSON 字符串转换成对象,或把对象转成 JSON
  3. 单元测试框架

    JUnit

    • 使用反射查找测试方法(被 @Test 注解的)
    • 然后在运行时调用这些方法进行测试

反射常用API

获取反射中的Class对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。

在 Java API 中,获取 Class 类对象有三种方法:

  1. Class.forName 静态方法

    • 适用场景:当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。不需要编译时已知类型
    1
    Class clz = Class.forName("java.lang.String");
  2. .class 方法

    • 适用场景:只能应用于编译时已知类型的 Class。
    1
    Class clz = String.class;
  3. 类对象的 getClass() 方法。

    • 适用场景:已有对象
    1
    2
    String str = new String("Hello");
    Class clz = str.getClass();

通过反射创建类对象

  1. 通过 Class 对象的 newInstance() 方法

    1
    2
    Class clz = Apple.class;
    Apple apple = (Apple)clz.newInstance();
  2. 通过 Constructor 对象的 newInstance() 方法

    1
    2
    3
    Class clz = Apple.class;
    Constructor constructor = clz.getConstructor();
    Apple apple = (Apple)constructor.newInstance();

区别:

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

1
2
3
Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

通过反射获取类属性、方法和构造器

  1. 通过 Class 对象的 getFields() 方法

    • 无法获取私有属性
    1
    2
    3
    4
    5
    Class clz = Apple.class;
    Field[] fields = clz.getFields();
    for (Field field : fields) {
    System.out.println(field.getName());
    }

    输出结果是:

    1
    price
  2. 使用 Class 对象的 getDeclaredFields() 方法

    • 可以获取包括私有属性在内的所有属性
    1
    2
    3
    4
    5
    Class clz = Apple.class;
    Field[] fields = clz.getDeclaredFields();
    for (Field field : fields) {
    System.out.println(field.getName());
    }

    输出结果是:

    1
    2
    name
    price

与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

参考文章