『Java』Java 反射机制
本文主要对 Java 反射机制原理、使用方法进行了介绍。
什么是 Java 反射?
通常,java在编译之后,会将Java代码生成为class源文件,JVM启动时,将会载入所有的源文件,并将类型信息存放到方法区中;将所有对象实例存放在Java堆中,同时也会保存指向类型信息的指针。
以下分两种情况来分析,直接使用类和使用反射的区别,以此理解反射的实现原理。
直接使用类
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
1 | Apple apple = new Apple(); //直接初始化,「正射」 |
上面这样子进行类对象的初始化,我们可以理解为「正」。
反射
在一个方法中,如果我们不知道在**实际运行(runtime)**时,它将要处理的对象是谁,它的类型信息是怎么样的(即在一开始并不知道我要初始化的类对象是什么),那我们如何访问这个对象或为这个对象创建一个新的实例呢?
与直接使用类相反,反射在运行时,通过读取方法区中的字节码,来动态的找到其反射的类以及类的方法和属性等(实际上就是在运行时,根据全类型名在方法区找对应的类),用这些类型信息完成对该类实例的操作,其实就是直接使用类的一个逆向使用。
例如,我们使用 JDK 提供的反射 API 进行反射调用:
1 | // 获取类的 Class 对象实例 |
上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。
类的正常加载过程和Java反射的过程,如图:
所以说什么是反射?
Java 反射机制是指程序在运行时才知道要操作的类是什么,并且能够在运行时动态地获取类的信息(如类名、方法、字段、构造函数等),并且可以动态地调用对象的方法或访问字段。
为什么需要Java反射?
Java 是静态语言,编译时就需要确定类的结构。这种方式虽然安全,但在需要高度灵活、动态扩展、解耦合的场景下就显得束手束脚。
反射机制的作用就是打破这种编译期限制,让你可以:
- 在运行时动态加载类
- 在运行时动态创建对象、访问字段、调用方法
- 提供更加通用、通配、框架化的设计
实际应用场景
-
插件系统、热插拔架构
插件(比如 IDEA 插件、游戏插件)通常是独立模块,不在主程序中直接引用。
主程序运行时通过反射加载这些类:
1
2
3
4Class<?> pluginClass = Class.forName("com.plugin.MyPlugin");
Object plugin = pluginClass.getDeclaredConstructor().newInstance();
Method run = pluginClass.getMethod("run");
run.invoke(plugin);这就实现了模块解耦,主程序无需编译时就知道所有插件。
-
序列化/反序列化框架
如 Jackson、Gson 等 JSON 解析器:
- 通过反射读取 Java 类的字段
- 动态地将 JSON 字符串转换成对象,或把对象转成 JSON
-
单元测试框架
如 JUnit:
- 使用反射查找测试方法(被
@Test
注解的) - 然后在运行时调用这些方法进行测试
- 使用反射查找测试方法(被
反射常用API
获取反射中的Class对象
在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:
-
Class.forName 静态方法
- 适用场景:当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。不需要编译时已知类型
1
Class clz = Class.forName("java.lang.String");
-
.class 方法
- 适用场景:只能应用于编译时已知类型的 Class。
1
Class clz = String.class;
-
类对象的 getClass() 方法。
- 适用场景:已有对象
1
2String str = new String("Hello");
Class clz = str.getClass();
通过反射创建类对象
-
通过 Class 对象的 newInstance() 方法
1
2Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance(); -
通过 Constructor 对象的 newInstance() 方法
1
2
3Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();
区别:
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。
1 | Class clz = Apple.class; |
通过反射获取类属性、方法和构造器
-
通过 Class 对象的 getFields() 方法
- 无法获取私有属性
1
2
3
4
5Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}输出结果是:
1
price
-
使用 Class 对象的 getDeclaredFields() 方法
- 可以获取包括私有属性在内的所有属性
1
2
3
4
5Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for ( : fields) {
System.out.println(; .getName())
}输出结果是:
1
2name
price
与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。