Java 反射是一个比较重要的知识点,你会在很多地方见到反射。它提供了 Java 语言在运行期间加载、探知和使用编译期间完全未知的类的能力。这种能力在框架的编写中非常常见,例如动态代理中、类扫描解析中。
反射机制:即 Java 语言在运行时有一种自观的能力,能够了解自身的情况并为下一步的动作做准备。反应出来就是,在运行时,对于一个类,我们能够知道该类有哪些方法和属性。对于一个对象,我们能够调用其任意的一个方法。这是一种动态获取类的信息以及动态调用对象方法的能力。
Java 提供反射机制,依赖于 Class 类和 java.lang.reflect 类库。其主要的类如下:
Class 类是 Java 中用来表示运行时类型信息的对应类。实际上在 Java 中每个类都有一个 Class 对象,每当我们编写并且编译一个新创建的类就会将相关信息写到 .class 文件里。当我们 new 一个新对象或者引用静态成员变量时,JVM 中的类加载器子系统会将对应 Class 对象加载到 JVM 中,然后 JVM 再根据这个类型信息相关的 Class 对象创建我们需要实例对象或者提供静态变量的引用值。我们可以将 Class 类,称为类类型,一个 Class 对象,称为类类型对象(参考 《Thinking in Java》)。
Class 类有以下的特点:
.class 文件存储了一个 Class 的所有信息,比如所有的方法,所有的构造函数,所有的字段(成员属性)等等。JVM 启动的时候通过 .class 文件会将相关的类加载到内存中,过程如下:
)
上面提到 Class 类只有一个私有的构造函数。所以无法通过 new 的方法获取 Class 实例。
/* * Constructor. Only the Java Virtual Machine creates Class * objects. */ private Class() {} 复制代码
可以通过 Class 的 forName 方法获取 Class 实例,其中类的名称要写类的完整路径。该方法只能用于获取引用类型的类类型对象。
// 这种方式会使用当前的类的加载器加载,并且会将 Class 类实例初始化 Class<?> clazz = Class.forName("java.lang.String"); // 上面的调用方式等价于 Class<?> clazz = Class.forName("java.lang.String", true, currentLoader); 复制代码
使用该方法可能会抛出 ClassNotFoundException 异常,这个异常发生在类的加载阶段,原因如下:
如果我们有一个类的对象,那么我们可以通过 Object.getClass 方法获得该类的 Class 对象。
// String 对象的 getClass 方法 Class clazz1 = "hello".getClass(); // 数组对象的 getClass 方法 Class clazz2 = (new byte[1024]).getClass(); System.out.println(class2) // 会输出 [B, [ 代表是数组, B 代表是 byte。即 byte 数组的类类型 复制代码
若我们知道要获取的类类型的名称时,我们可以使用 class 语法获取该类类型的对象。
// 类 Class clazz = Integer.class; // 数组 Class clazz2 = int [][].class; 复制代码
对于基本类型和 void 都有对应的包装类。在包装类中有一个静态属性 TYPE,保存了该来的类类型。以 Integer 类为例,其源码中定义了如下的静态属性:
/** * The {@code Class} instance representing the primitive type * {@code int}. * * @since JDK1.1 */ @SuppressWarnings("unchecked") public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); 复制代码
生成 Class 类实例的方法:
Class clazz1 = Integer.TYPE; Class clazz2 = Void.TYPE; 复制代码
Class 中有获取其他 Class 的方法,列举如下:
在讲 Field、Method、Constructor 之前,先说说 Member 和 AccessibleObject。Member 是一个接口,表示 Class 的成员,前面的三个类都是其实现类。
AccessibleObject 是 Field、Method、Constructor 三个类共同继承的父类,它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。通过 setAccessible 方法可以忽略访问级别,从而访问对应的内容。并且 AccessibleObject 实现了 AnnotatedElement 接口,提供了与获取注解相关的能力。
Field 提供了有关类或接口的单个属性的信息,以及对它的动态访问的能力。
可以通过 Class 提供的方法,获取 Field 对象,具体如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Field |
getDeclaredField(String name) |
获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
Field[] |
getDeclaredField() |
获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 |
Field |
getField(String name) |
获取指定name名称、具有public修饰的字段,包含继承字段 |
Field[] |
getField() |
获取修饰符为public的字段,包含继承字段 |
Field 相关的 API 我就不全局列举出来了,可以点击这里查看。
Method 提供了有关类或接口的单个方法的信息,以及对它的动态访问的能力。
可以通过 Class 提供的方法,获取 Field 对象,具体如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Method |
getDeclaredMethod(String name, Class<?>... parameterTypes) |
返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Method[] |
getDeclaredMethod() |
返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
Method |
getMethod(String name, Class<?>... parameterTypes) |
返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
Method[] |
getMethods() |
返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
Method 相关的 API 可以点击这里查看。
Constructor 提供了有关类的构造方法的信息,以及对它的动态访问的能力。
可以通过 Class 提供的方法,获取 Constructor 对象,具体如下:
方法返回值 | 方法名称 | 方法说明 |
---|---|---|
Constructor<T> |
getConstructor(Class<?>... parameterTypes) |
返回指定参数类型、具有public访问权限的构造函数对象 |
Constructor<?>[] |
getConstructors() |
返回所有具有public访问权限的构造函数的Constructor对象数组 |
Constructor<T> |
getDeclaredConstructor(Class<?>... parameterTypes) |
返回指定参数类型、所有声明的(包括private)构造函数对象 |
Constructor<?>[] |
getDeclaredConstructor() |
返回所有声明的(包括private)构造函数对象 |
Constructor 相关的 API 我就不全局列举出来了,可以点击这里查看。
在 Java 中数组也是一种类,Array 提供了动态创建数组和访问数组元素的静态方法。
通过 getXXX(Object array, int index)
方法,传入数组对象和下标索引,可以获取到该位置的值。
通过 newInstance(Class<?> componentType, int length)
方法,传入数组类型和长度,创建数组。如下:
// 下面创建的两个数组等价 int x[] = new int[10]; int y[] = (int[]) Array.newInstance(int.class, 10); // 输出 true System.out.println(x.length == y.length); 复制代码
通过 newInstance(Class<?> componentType, int... dimensions)
方法,创建多维数组。如下:
// 下面创建的两个数组等价 int x[][] = new int[10][10]; int y[][] = (int[][]) Array.newInstance(int.class, 10, 10); // 输出 true System.out.println(x.length == y.length); 复制代码