反射
反射库( reflection library ) 提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵 Java 代码的程序。这项功能被大量地应用于 JavaBeans中,它是 Java组件的体系结构。
能够分析类能力的程序称为反射(reflective )。反射机制的功能极其强大,在下面可以看到,反射机制可以用来:
- 在运行时分析类的能力。
- 在运行时查看对象,例如,编写一个 toString方法供所有类使用。
- 实现通用的数组操作代码。
- 利用 Method对象,这个对象很像中的函数指针。
class类
最常用的 Class方法是 getName。这个方法将返回类的名字。如果类在一个包里,包的名字也作为类名的一部分:包名.类名。
还可以调用静态方法 forName 获得类名对应的 Class 对象。
String dassName = "java.util .Random";
Class cl = Class.forName(className) ;
这个方法只有在 className是类名或接口名时才能够执行。否则,forName方法将抛出一个 checked exception (已检查异常)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler ) o。
请注意,一个 Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int 不是类,但 int.class 是一个 Class类型的对象。
Class 类实际上是一个泛型类。例如, Employee.class 的类型是 Class
虚拟机为每个类型管理一个 Class 对象。因此,可以利用=运算符实现两个类对象比较的操作。例如,if (e.getClass() == Employee,class) . . .
还有一个很有用的方法 newlnstance( ),可以用来动态地创建一个类的实例例如:e.getClass0.newlnstance();
创建了一个与 e具有相同类类型的实例。
newlnstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器,就会抛出一个异常。
捕获异常
当程序运行过程中发生错误时,就会“抛出异常“抛出异常比终止程序要灵活得多,这是因为可以提供一个“ 捕获” 异常的处理器(handler ) 对异常情况进行处理。如果没有提供处理器,程序就会终止,并在控制台上打印出一条信息,其中给出了异常的类型。
异常有两种类型:未检查异常和已检查异常。
如果try块中抛出异常,则将跳过 try块中的剩余代码,程序直接进人catch 子句(这里,利用Throwable类的 printStackTrace 方法打印出栈的轨迹。Throwable 是 Exception 类的超类)。如果try块中没有抛出任何异常,那么会跳过 catch 子句的处理器代码。
利用反射分析类的能力
反射机制最重要的内容—检查类的结构。
- 在java.lang.reflect 包中有三个类 Field、Method 和 Constructor分别用于描述类的域、方法和构造器。
- Field类有一 个getType方法,用来返回描述域所属类型的 Class 对象。
- Method类还有一个可以报告返回类型的方法。
- Method 和 Constructor类有能够 报告参数类型的方法
- 这三个类都有一个叫做 getName 的方法,用来返回项目的名称。
- 这三个类还有一个叫做 getModifiers 的方法,它将返回一个整型数值,用不同的位开关描述 public 和 static 这样的修饰符使用状况。
- 另外,还可以利用java.lang.reflect 包中的 Modifier类的静态方法分析 getModifiers 返回的整型数值。
- 例如,可以使用 Modifier类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final。
- 还可以利用 Modifier.toString方法将 修饰符打印出来。
- Class类(java.lang.Object下java.lang.Class
) - Class类中的 getFields、 getMethods 和 getConstructors方法将分别返回类提供的public域、方法和构造器数组,其中包括超类的公有成员。
- Class 类的 getDeclareFields、 getDeclareMethods 和 getDeclaredConstructors方法将分别返回类中声明的全部域、方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
在运行时使用反射分析对象
查看任意对象的数据域名称和类型:
- 获得对应的 Class 对象。
- 通过 Class 对象调用 getDeclaredFields。
利用反射机制可以查看在编译时还不清楚的对象域。
查看对象域的关键方法是 Field类中的 get 方法:如果 f 是一个 Field类型的对象(例如,通过 getDeclaredFields 得到的对象),obj 是某个包含 f域的类的对象,f.get(obj)将返回一个对象,其值为 obj 域的当前值。
Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989) ;
Class cl = harry.getClass0;
// the class object representing Employee
Field f = cl .getDeclaredFieldC'name") :
// the name field of the Employee class
Object v = f.get (harry);
// the value of the name field of the harry object,i .e., the String object "Harry Hacker"
实际上,这段代码存在一个问题:如果name是一个私有域,那么get 方法将会抛出一个 IllegalAccessException。
反射机制的默认行为受限于 Java 的访问控制。除非拥有访问权限,否则 Java 安全机制只允许査看任意对象有哪些域,而不允许读取它们的值.
然而,如果一个 Java 程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用 Field、Method 或 Constructor 对象的 setAccessible 方法。
setAccessible方法是 AccessibleObject类中的一个方法,它是 Field、 Method 和 Constructor类的公共超类。这个特性是为调试、持久存储和相似机制提供的。
可以获得就可以设置。调用 f.set(obj,value) 可以将 obj 对象的 f 域设置成新值。
编写一个可供任意类使用的通用 toString方法:使用getDeclaredFileds 获得所有的数据域,然后使用setAccessible 将所有的域设置为可访问的。对 于每个域,获得了名字和值。
PS:循环引用将有可能导致无限递归。因此,ObjectAnalyzer将记录已经被访问过的对象。
为了能够査看数组内部,需要采用一种不同的方式。
可以使用 toString方法查看任意对象的内部信息。例如,下面这个调用:
ArrayList<Integer> squares = new ArrayList<>();
for (int i = 1; i <= 5; i++) squares.add(i * i); System.out.println(new ObjectAnalyzer().toString(squares));
将会产生下时的打印结果:
java.util.Arraylist[elementData=class java.lang.Object[]{java.Intager[value=1][][],java.1ang.Integer[value=4][][],java.1ang.Integer[value=9][][],java.1ang.Integer[value=16][][],java.1ang.Integer[value=25][][],null,null,null,null,null},size=5][modCount=5][][]
还可以使用通用的 toString 方法实现自己类中的 toString 方法, 如下所示:
public String toString() {
return new ObjectAnalyzer().toString(this);
}
这是一种公认的提供 toString 方法的手段。
使用反射编写泛型数组代码
java.lang.reflect 包中的 Array类允许动态地创建数组。例如,将这个特性应用到 Array类中的 copyOf方法实现中。
将一个 Employee[]临时地转换成 Object[]数组,然后再把它转换回来是可以的,但一从开始就是 Object[] 的数组却永远不能转换成 Employee[]数组。
Array类中的静态方法 newlnstance, 它能够构造新数组。在调用它时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。
Object newArray = Array.newlnstance(componentType, newLength) ;
可以通过调用 Array.getLength(a) 获得数组的长度。
而要获得新数组元素类型,就需要进行以下工作:
- 首先获得 a数组的类对象。
- 确认它是一个数组。
- 使用 Class类(只能定义表示数组的类对象)的 getComponentType确定数组对应的类型。
public static Object goodCopyOf(Object a, int newLength) {
Class cl = a.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(a);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(a, 0, Math.min(length,newLength));
return newArray;
}
请注意,这个 CopyOf方法可以用来扩展任意类型的数组,而不仅是对象数组,基本类型数组也可。
intn a = { 1,2, 3, 4, 5 };
a = (int[]) goodCopyOf(a, 10);
整型数组类型 int[] 可以被转换成 Object,但不能转换成对象数组,所以goodCopyOf的参数声明为 Object 类型,而不要声明为对象型数组(Object[])。
java.lang.reflect.Array 1.1
- static Object get(Object array,int index)
- static xxx getxxx(Object array,int index)
(xxx 是 boolean、byte、char、 double、 float、int、 long、 short 之中的一种基本类型。)
这些方法将返回存储在给定位置上的给定数组的内容。- static void set(Object array,int index,Object newValue)
- static setXxx(Object array,int index,xxx newValue)
( xxx 是 boolean、 byte、char、double、float、int、 long、short 之中的一种基本类型。)
这些方法将一个新值存储到给定位置上的给定数组中。- static int getLength(Object array)
返回数组的长度。- static Object newInstance(Class componentType,int length)
- static Object newInstance(Class componentType, int[] lengths)
返回一个具有给定类型、给定维数的新数组
调用任意方法
在 Method类中有一个 invoke 方法,它允许调用包装在当前 Method 对象中 的方法。invoke 方法的签名是:
Object invoke(Object obj, Object... args)
第一个参数是隐式参数,其余的对象提供了显式参数。
对于静态方法,第一个参数可以被忽略,即可以将它设置为 null。
例如,假设用 ml 代表 Employee类的 getName方法,下面这条语句显示了如何调用这个方法:
String n = (String) ml.invoke(harry);
如果返回类型是基本类型, invoke方法会返回其包装器类型。
如何得到 Method对象呢?
-
可以通过调用 getDeclareMethods方法,然后对返回的 Method对象数组进行查找,直到发现想要的方法为止。
-
也可以通过调用Class类中的 getMethod方法得到想要的方法。它与 getField方法类似。需要传入方法名和参数类型。
Method getMethod(String name, Class... parameterTypes)
例如,下面说明了如何获得 Employee类的 getName方法和 raiseSalary方法的方法指针。Method ml = Employee.class.getMethod("getName"); Method m2 = Employee.class.getMethod("raiseSalary",double.class);