反射
反射机制提供了如下一些功能:
在运行时分析类。
在运行时查看对象。
实现通用的数组操作代码。
利用
Method
对象。
由于反射机制的权限较高,在利用其查看运行时错误时,也要注意是否破坏了程序的封装性。
Class类
实际上,Class
负责保存类的信息,而不是对象。虚拟机利用运行时类型信息选择相应的方法执行。
在Object
类中的getClass
方法会返回一个Class
类型的实例。因此,可以令一个对象调用getClass
方法来获取这个对象所属类的信息。其中最常见的类名可以通过getName
方法来获取,类所在的包也包含在类名中。
// 假设e是某个类的实例
System.out.println(e.getClass().getName());
相反地,可以通过forName
方法获取类名对应的Class
实例。
// 这里要获取Random的Class实例,注意包名
Class cl = Class.forName("java.util.Random");
// 如果输入的类名不存在,会抛出“已检查异常”ClassNotFoundException。
还有一种很简单的方法来获取Class
类对象,即直接在Java类型或void
关键字后加上.class
,获得匹配的类对象。
Class cl_1 = java.util.Random.class;
Class cl_2 = int.class;
Class cl_3 = Double[].class;
以上是获取Class
类对象的三种方法。
获取Class
类对象后,可以调用newInstance
方法来调用类的默认构造器,创建类的一个实例。这里要求这个类必须有默认的构造器,否则会抛出异常。如果希望使用使用包含参数的构造器,就要调用Constructor
类中的newInstance
方法。
利用反射分析类的能力
在java.lang.reflect
包中的类Field
、Method
和Constructor
用于描述类的域、方法和构造器。Modifier
类提供了static
方法和常量,可以对类和成员的访问修饰符进行解码。
在Class
类中的getXXX
方法和getDeclaredXXX
方法所返回的对象数组有一定的区别:前者返回公有和超类的(如果有超类),后者返回所有的(不包含超类)。
注:Constructor
和Method
类中的getParameterTypes
方法可以返回构造器或方法的参数的类型的Class
对象数组。Method
类中的getReturnType
方法返回方法的返回参数类型的Class
对象。
在Modifier
类中有一系列的static boolean
方法用于检测方法的修饰符。getModifiers
方法会返回修饰符的int
位模式存储,每一位代表一种修饰符。Modifier
类还提供了toString
方法,返回修饰符位模式对应的字符串表示。
在运行时使用反射分析对象
如果已经获得了确定对象的某个域对象,可以调用Field
类中的get(Object obj)
方法来获取某个对象中该域的当前值,并存储在相应类的对象中返回。
// harry是一个员工对象,有一个域name
Employee harry = new Employee("Harry Hacker");
// 通过getClass获取harry所属的类的Class对象cl
Class cl = harry.getClass();
// 通过getDeclaredField("name")获取cl中存储的name域的Field对象f
Field f = cl.getDeclaredField("name");
// f调用get(harry)获取harry这个对象的name域的当前值,并存储在Object对象v中
Object v = f.get(harry);
// v的输出结果是Harry Hacker
这里name
是私有域,get
方法会抛出illegalAccessException
。可以调用setAccessible
方法覆盖Java
的访问控制,使反射机制的默认行为不再受限。
f.setAccessible(true);
使用反射编写泛型数组代码
利用java.lang.reflect
包中的Array
类可以动态地扩展已经创建的数组。由于一个数组的元素类型在这个数组被创建时就已经被记录了,无法将Object
对象数组转换为需要的类对象数组。因此,需要利用Array
类中的静态方法newInstance(componentType, newLength)
来构造一个所需类的对象数组,这样在之后的扩展过程中,可以将这个数组转换为Object
类后再转换为原来的类。扩展操作如下:
public static Object CopyOf(Object a, int newLength)
{
// 首先获取对象a的Class对象cl
Class cl = a.getClass();
// 判断a是否是Array类对象
if (!cl.isArray()) return null;
// 确定数组对应的类型
Class componentType = cl.getComponentType();
// 获取数组a的长度
int length = Array.getLength(a);
// 构造一个和a的类型相同的新Array数组,长度为newLength
Object newArray = Array.newInstance(componentType, newLength);
// 将a的内容复制到新的Array数组中(有一种realloc的既视感)
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
调用任意方法
利用Method
类中的invoke
方法可以调用任何对象的方法。invoke
的签名如下:
// 第一个参数obj是隐式参数,对于静态方法,被设置为null,后面的显式参数表示被调用的对象数组。
Object invoke(Object obj, Object... args)
由于隐式的参数实际上是Method
对象,所以需要调用getMethod
来获取想要调用的方法的Method
对象。
// 第一个参数是方法名的字符串,后面的显式参数表示方法的参数类型,用来辨别同名不同参数的方法。
Method getMethod(String name, Class... parameterTypes)
建议:仅在必要时使用Method
对象,最好使用接口以及lambda
表达式。