0%

Java专题:反射

概述

通过反射 API 可以获取 Java 程序在运行时刻的内部结构,并可与其进行交互。

反射是 Java 动态性的一种体现。

反射方法调用的合法性由开发人员自己保证。如果方法调用不合法,则相关异常会在运行时抛出。

反射 API 为 Java 程序带来灵活性,但也产生了额外的性能代价。与相同的操作相比,反射大概慢一到两个数量级。

基本步骤:

  1. 获取目标类型相应的 Class 对象;
  2. 调用 Class 对象内省方法获取目标类型成员信息;
  3. 访问目标类型成员信息/操作目标类型中成员。

类型对象

1
2
3
4
5
6
7
8
// 获取 Class 对象
Class<?> clazz = Class.forName(className);
// Object.getClass();
// Object.class

Class<?> superclass = clazz.getSuperclass();
Class<?>[] interfaces = clazz.getInterfaces(); // 仅包括本类实现的接口
Package pkg = clazz.getPackage();

构造方法

1
2
3
4
5
6
7
8
9
10
11
Constructor<?>[] constructors = clazz.getConstructors(); // 所有公有构造器
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors(); // 所有声明的构造器
Constructor<?> constructor = clazz.getConstructor(parameterTypes);
Constructor<?>[] declaredConstructor = clazz.getDeclaredConstructor(parameterTypes);

String modifiers = Modifier.toString(constructor.getModifiers());
String name = constructor.getName();
Class<?>[] parameterTypes = constructor.getParameterTypes(); // 类型变量

clazz.newInstance();
constructor.newInstance(parameters);

可变参数

如果构造方法声明了长度可变的参数,在获取构造方法的时候,要使用对应的数组类型的 Class 对象。 这是因为长度可变的参数实际上是通过数组来实现的。

在调用 newInstance 的时候,要把作为实际参数的字符串数组先转换成为 Object 类型,这是为了避免方法调用时的歧义。 这样编译器就知道把这个字符串数组作为一个可变长度的参数来传递。

嵌套类(nested class)

对于非静态嵌套类来说,其特殊之处在于它的对象实例中都有一个隐含的对象引用,指向包含它的外部类对象。 也正是这个隐含的对象引用的存在,使非静态嵌套类中的代码可以直接引用外部类中包含的私有域和方法。 因此,在获取非静态嵌套类的构造方法的时候,类型参数列表的第一个值必须是外部类的 Class 对象。

1
2
3
4
5
6
7
8
9
10
11
12
Field[] fields = clazz.getFields(); // 所有公有域(包括继承的)
Field[] declaredFields = clazz.getDeclaredFields(); // 当前类声明的所有域
Field field = clazz.getField(name);
Field declaredField = clazz.getDeclaredField(name);

String modifiers = Modifier.toString(field.getModifiers());
Class<?> fieldType = Field.getType();
String name = field.getName();

instanceField.setAccessible(true);
instanceField.set(instance, value);
staticField.set(null, value);

方法

1
2
3
4
5
6
7
8
9
10
11
12
13
Method[] methods = clazz.getMethods(); // 所有公有方法(包括继承的)
Method[] declaredMethods = clazz.getDeclaredMethods(); // 当前类声明的所有方法
Method method = clazz.getMethod(name);
Method declaredMethod = clazz.getDeclaredMethod(name);

String modifiers = Modifier.toString(method.getModifiers());
Class<?> returnType = method.getReturnType();
String name = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes(); // 类型变量

method.setAccessible(true);
instanceMethod.invoke(instance, parameters);
staticMethod.invoke(null, parameters);

注解

1
2
clazz.getAnnotations(); // 所有注解(包括继承的)
clazz.getDeclaredAnnotations(); // 当前类声明的注解

数组

1
2
3
4
5
Array.newInstance();
Array.set(array, value);
Array.getLength(array);
Array.get(array, index);
Clazz.isArray();

异常

在利用 invoke 方法来调用方法时,如果方法本身抛出了异常,invoke 方法会抛出 InvocationTargetException 异常来表示这种情况。 在捕获到 InvocationTargetException 异常的时候,通过 InvocationTargetException 异常的 getCause 方法可以获取到真正的异常信息,帮助进行调试。

Java 7 为所有与反射操作相关的异常类添加了一个新的父类 java.lang.ReflectiveOperationException。在处理与反射相关的异常的时候,可以直接捕获这个新的异常。 而在 Java 7 之前,这些异常是需要分别捕获的。