Java 的反射机制是在运行状态中,对于任意类,我们能获取这个类的所有属性和方法;对于任意对象,我们能使用它的所有属性和调用所有方法;这种动态获取对象的类型信息、动态获取对象属性和动态调用对象方法的功能我们称为反射机制。
反射的功能由 Class 类提供支持;反射所获取信息的存储由 java.lang.reflect
包中的类提供支持。
- 本文所有信息基于作者实践验证,如有错误,恳请指正,不胜感谢。
- 转载请于文首标明出处:【Java】反射机制
- 文章仍未完工,内容会逐步完善
Class 类型
我们所创建的一切类,无论是静态类还是内部类,在经由 JVM 类加载之后,都会在方法区中生成类的所有信息,同时会在堆中生成一个 Class
对象,作为方法区中类信息的入口。Java 定义类 Class
对象作为 Java 中一切类型的抽象。Class
类型就定义在 java.lang
包下,由于 Class
对象操作的是方法区中真正代表类的数据结构,因此绝大多数 Class
类的方法都是本地方法:
package java.lang;
public final class Class<T> implements ... {}
这一设计是“一切皆对象”思想的体现。一切事物都能抽象成对象 Object
,同一种事物可以抽象成类 class
,所有事物的类型可以抽象成 Class
,每一种类型,即每一个类,都是 Class
的对象。
实际上不止 class
存在抽象,在 Java 中的一切,包括字段、方法、构造方法、注解、接口、父类型、泛型类型、泛型参数甚至是基本数据类型等等,都存在各自抽象的类型。
同时,Class
类型也是 运行时类型识别(Run-Time Type Identification,RTTI) 的实现,RTTI 就是要在运行时确定对象的类型。Java 的实例对象在运行时存储在堆中,由对象头、对象数据和填充数据组成,对象头中就包含了指向其 Class
对象的类型指针,JVM 就是通过这个指针来实现运行时类型识别的。同一个类的实例的类型指针指向同一个类对象,所有类对象的类型指针又指向了 Class
类对象。
获取类对象
获取类对象的方式共三种,分别是通过 class
关键字、通过对象的 Object.getClass
方法和通过 Class::forName
方法指定类全限定名来获取类对象。类对象是方法区中类数据结构的入口,因此如果在运行中动态获取对象的类型并进行任何操作,都必须先获取类对象。
// 通过 class 关键字
Class<String> clazz = String.class;
// 通过 Object.getClass 方法
String string = "HELLO WORLD";
Class<?> clazz = string.getClass();
// 通过 Class::forName 方法
Class<?> clazz = Class.forName("java.lang.String");
可以看到后面二者返回的类型是 Class
的泛型类型 Class<?>
,这是因为 Object.getClass
和 Class::forName
是通过对象和全限定名来获取类,在编译时并不知道这究竟可能是什么对象,也不知道这个全限定名所指的类存不存在,只有在运行时才能确定,因此返回的是一个泛型类型。
而 String.class
这种方式,是通过类来获取类对象的,在编译期就能确定这具体是什么类型了。
反射创建实例
通过反射创建类实例有两种方式,分别是直接创建和获取类的构造方法创建。
// 通过 Class.newInstance 创建
String instanceByClass = String.class.newInstance();
// 通过 Constructor.newInstance 创建
Constructor<String> constructor = String.class.getDeclaredConstructor();
String instanceByConstructor = constructor.newInstance();
直接创建实际上就是调用了无参构造方法来创建,是一层封装。如果目标类型没有定义无参构造方法,或者没有访问权限,则会失败报错。
通过构造方法的方式会更灵活可靠,如果目标构造方法没有访问权限,可以使用 Constructor.setAccessable(true)
强制访问。
public final class Class<T> {
// 获取所有
// 获取指定
}
获取字段
public final class Class<T> {
// 获取所有字段
public Field[] getDeclaredFields() {}
// 获取指定字段
public Field getDeclaredField(String name) {}
}
其实还有
Class.getFields()
和Class.getField(String name)
方法也可以获取方法字段,但在Class
中,没有Declared
定语修饰的方法全部都只能获取public
修饰的属性,包括字段、方法、构造方法等。
获取方法
方法的唯一性确定由方法特征签名确定,《Java 语言规范》中定义的方法特征签名包括方法名称、参数顺序和参数类型。因此,要通过反射获取类的特定方法,必须要指定方法名和参数列表。
方法特征签名详见:【Java】主要思想与易混淆概念 (juejin.cn)
public final class Class<T> {
// 获取所有方法
public Method[] getDeclaredMethods() {}
// 获取指定方法
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) {}
}
获取构造方法
构造方法其实也是类的方法,由于构造方法名称必须和类名相同,获取类的构造方法不需要指定方法名。
public final class Class<T> {
// 获取所有构造方法
public Constructor<?>[] getDeclaredConstructors() {...}
// 获取指定构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) {...}
}
获取类的接接口
获取类的父类
获取类的注解
获取类的泛型
ParameterizedType genericSuperclass = (ParameterizedType) MyDataStructure.class.getGenericSuperclass();
Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
// 第一个泛型参数
Type genericArgument1 = actualTypeArguments[0];
// 第二个泛型参数
Type genericArgument2 = actualTypeArguments[1];
// 第三个泛型参数
Type genericArgument2 = actualTypeArguments[2];
看一个实际代码的使用示例,一个 MyBatisPlus ServiceImpl
的增强类,有两个泛型参数,我们需要获取表实体对象 T
的类型,并获取它的表名:
public class BaseServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements BaseService<T> {
@Override
public final long approximateCount() {
Type[] genericParameters = ((ParameterizedType) this.getClass().getGenericSuperclass())
.getActualTypeArguments();
Class<?> tableEntityClass = (Class<?>) genericParameters[0];
TableName annotation = tableEntityClass.getAnnotation(TableName.class);
Assert.notNull(annotation, "generic type class(" + tableEntityClass + ") @TableName annotation get failed");
String tableName = annotation.value();
...
}