反射
Class 类
Java 程序运行期间,JVM 会为每一个类维护一个(仅一个)Class
的实例,该实例用来描述类的信息。
获取实例
每个类都会从顶级类中继承这个方法,用于返回该类唯一的Class
实例。
Class getClass();
或者使用Class
的静态方法,通过字符串得到Class
实例:
static Class forName(String className);
最后一种方法是通过class
关键字,例如:
Integer.class;
int.class;
// 基本类型也有 Class 实例
此外,Class
有一个方法,用于调用该类的无参构造从而得到一个新实例:
Object newlnstance();
Field、Method 和 Constructor 类
获得他们的实例
这些类用来描述域、方法和构造器。
通过Class
里的方法,返回对应数组:
Field[] getFields();
// 返回该类及超类的公有域
Filed[] getDeclaredFie1ds();
// 返回该类的全部域
Method[] getMethods();
// 返回该类及超类的公有方法
Method[] getDeclareMethods();
// 返回该类及接口的全部方法
Constructor[] getConstructors();
// 返回全部公有构造器
Constructor[] getDeclaredConstructors();
// 返回所有构造器
或者通过字符串描述来获得特定的实例:
Field[] getField(String name);
Filed[] getDeclaredFie1d(String name);
// 通过域名获取 Field 实例
Method[] getMethod(String name, Class<?>... parameterTypes);
Method[] getDeclareMethod(String name, Class<?>... parameterTypes);
// 通过方法名及参数列表的 Class 实例获取 Method 实例
Constructor[] getConstructor(Class<?>... parameterTypes);
Constructor[] getDeclaredConstructor(Class<?>... parameterTypes);
// 通过参数列表的 Class 实例获取 Constructor 实例
名称
Field、Method 和 Constructor 类都有一个getName
方法来获得名称。
String getName();
修饰符
还有一个getModifiers
方法来得到修饰符。
int getModifiers();
返回的 int 包含所有修饰符的是否使用的状态,需要通过Modifier
类中的静态方法来解析:
static String toString(int modifiers);
static boolean isAbstract(int modifiers);
static boolean isFinal (int modifiers);
static boolean islnterface(int modifiers);
static boolean isNative(int modifiers);
static boolean isPrivate(int modifiers);
static boolean isProtected(int modifiers);
static boolean isPubl(int modifiers);
static boolean isStat(int modifiers);
static boolean isStrict(int modifiers);
static boolean isSynchronized(int modifiers);
static boolean isVolatile(int modifiers);
fields 的类型
特别地,Field 拥有一个getType
方法,来获得域的Class
实例。
Class getType();
参数列表及异常
Constructor 和 Method 类拥有getParameterTypes
和getExceptionTypes
方法,来获得参数列表的类型及抛出的异常类型。
Class[] getParameterTypes();
Class[] getExceptionTypes();
返回值类型
Method 拥有一个getReturnType
类,获得返回值的类型。
Class getReturnType();
运行时操作
我们知道,Java 特性有可能是编译器特性,也可能是解释器特性。
例如枚举类enum
的values
方法将返回所有枚举实例的数组,不过很少有人去思考这个数组究竟存在于何处,以为是什么可怕的魔法。然而这实际上是编译器的行为。
我们通过反射来验证。
对于枚举类:
enum State {
Success, Fail;
}
我们拿到该类的域数组,然后打印出它们的名称:
Arrays.asList(State.class.getDeclaredFields())
.forEach(item -> System.out.println(item.getName()));
输出:
Success
Fail
$VALUES
出现了一个奇怪的符号$VALUES
,实际上它就是values
方法返回的数组。
下面介绍几个可怕的方法,来操作我们本来无法操作的域:
运行时操作域
Field 的方法:
Object get(Object obj);
// 传入一个实例,返回该实例的域值
void set(Object obj ,Object newValue);
// 修改某个实例下该域的值
在查看$VALUES
之前,我们还有一个安全机制的问题需要讨论。
如果$VALUES
是一个私有域,那我们岂不是绕过了类的访问检查?到目前为止并不会,如果你看到这里就已经试图去获取该域值,那么会抛出一个IllegalAccessException
异常。
可以使用 Field 的setAccessible
方法来设置可访问标识:
void setAccessible(boolean flag);
// 为反射对象设置可访问标志,flag 为 true 表明屏蔽 Java 语言的访问检查,使得对象的私有属性也可以被 get 和 set
boolean isAccessible();
// 返回反射对象的可访问标志的值
static void setAccessible(AccessibleObject[] array, boolean flag);
// 批量为反射对象设置可访问标志
反射对象指 Field、Method 和 Constructor 等类。
现在,我们真的已经绕过了类的访问检查。
$VALUES
确实是一个私有域,可以通过前面提到过的Modifier
类来验证:System.out.println(Modifier.isPrivate(State.class.getDeclaredField("$VALUES").getModifiers()));
输出:
true
测试:
注意,这两个方法必须进行异常处理。
try {
Field values = State.class.getDeclaredField("$VALUES");
values.setAccessible(true);
Arrays.asList((State[]) values.get(State.Success))
.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
输出:
Success
Fail
运行时构造对象
由于 Java 禁止不安全的类型转换(向下转型),所以实现通用的方法就很困难。
通用的方法,意味着对任意类型均可用。例如一个接受 Object 参数的方法,此时若需要构造一个该类型的新实例该怎么办呢?
使用Constructor
的静态方法newInstance
:
它不同于Class
的newInstance
,Constructor
的允许调用非空参构造器。
Object newlnstance(Object[] args);
现在看一个例子,实现Arrays.copyOf
方法:
想要实例化一个数组,可以使用 Array 的一个静态方法:
static Object newInstance(Class<?> componentType, int length);
// 通过数据类型和长度构造一个数组
第一个参数通过Class
的getComponentType
方法获得:
Class getComponentType();
实现如下:
通过多态取到元素类型的Class
实例,然后调用Array.newInstance
实例化一个数组。
public static Object[] expend(Object[] arr, int length) {
Object[] newArr = (Object[]) Array.newInstance(arr.getClass().getComponentType(), length);
for (int i = 0; i < Math.min(arr.length, newArr.length); i++) {
newArr[i] = arr[i];
}
return newArr;
}
方法指针
方法指针指的是 Method 类的实例。关于获得 Method 实例的几种方法,不管是获取特定的还是获取全部的,都在本节开头详细罗列了。
每个 Method 实例都有一个 invoke
方法,用来调用该 Method 对象描述的方法:
Object invoke(Object obj, Object... args)
第一个参数是方法的隐式参数,即this
指向的对象,或理解为调用该方法的对象。接下来的是该方法的参数列表。
对于静态类型,第一个参数可以传入null
,或者传入任意值(毕竟在静态方法中不需要使用this
)。
这个invoke
方法类似 Fields 中的get
和set
方法,对于访问受限的方法,我们也需要对相应 Method 实例调用setAccessible
方法,绕过访问检查。
如非必要,不要使用函数指针来调用方法,很麻烦,且容易出错。
如果需要回调函数,应该使用接口或 lambda。