使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。
day035 反射(二)在运行时使用反射分析对象、使用反射编写泛型数组代码、调用任意方法
1.在运行时使用反射分析对象
在前面,已经知道如何查看任意对象的数据域名称和类型:
•获得对应的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.getClass();
//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。只有利用get方法才能得到可访问域的值。除非拥有访问权限,否则Java安全机制只允许査看任意对象有哪些域,而不允许读取它们的值。
反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field、Method或Constructor对象的setAccessible方法。例如,
f.setAtcessible(true);//now OK to callf.get(harry);
setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的公共超类。这个特性是为调试、持久存储和相似机制提供的。稍后将利用它编写一个通用的toString方法。
get方法还有一个需要解决的问题。name域是一个String,因此把它作为Object返回没有什么问题。但是,假定我们想要查看salary域。它属于double类型,而Java中数值类型不是对象。要想解决这个问题,可以使用Field类中的getDouble方法,也可以调用get方法,此时,反射机制将会自动地将这个域值打包到相应的对象包装器中,这里将打包成Double。
当然,可以获得就可以设置。调用f.set(obj,value)可以将obj对象的f域设置成新值。
后面的程序1显示了如何编写一个可供任意类使用的通用toString方法。其中使用getDeclaredFileds获得所有的数据域,然后使用setAccessible将所有的域设置为可访问的。对于每个域,获得了名字和值。后面的程序2递归调用toString方法,将每个值转换成字符串。泛型toString方法需要解释几个复杂的问题。循环引用将有可能导致无限递归。因此,ObjectAnalyzer将记录已经被访问过的对象。另外,为了能够査看数组内部,需要采用一种不同的方式。
可以使用toString方法查看任意对象的内部信息。例如,下面这个凋叫:
ArrayList<Tnteger> squares=new ArrayList<>();
for(inti=1;i<=5;i++)
squares.add(i*i);
System.out.println(new ObjectAnalyzer().toString(squares));
将会产生下时的打印结果:
java.util.ArrayList[elementData=classjava.1ang.Object[]{java.1ang.Integer[value=l][][],
java.1ang.Integer[value=4][][],java.1ang.Integer[value=9][][],java.1ang.I