操作对象属性(域)的值
前面已经知道如何查看任意对象的数据域名称和类型。在编写程序时,如果想要查看域名和类型是很简单的事情,而反射机制可以查看在编译时还不清楚的对象域。
利用get方法获取域(属性)值
查看对象域的关键方法是Field
类的get
方法。如果f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,类型为Object,其值为obj域的当前值。示例:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; class Person { private String name; public Person(String name) { this.name = name; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person("萧萧弈寒"); Class clz = p.getClass(); Field f = clz.getDeclaredField("name"); //f.setAccessible(true); Object name = f.get(p); System.out.println(name); } }
【运行结果】:
Exception in thread "main" java.lang.IllegalAccessException: Class com.xiaoxiaoyihan.reflection.ReflectionDemo2 can not access a member of class com.xiaoxiaoyihan.reflection.Person with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:109)
……
说明:因为反射机制的默认行为受限于Java的访问控制。如果一个Java程序没有受限于安全管理器的控制,就可以覆盖访问控制。Field,Method或Constructor对象提供了
setAccessible
方法用于覆盖访问控制。
get
方法还需要解决另一个问题,get方法的返回值是Object类型的,上面的name是String类型的,将String类型值,赋给Object对象没有问题,但是如果出现double类型的域呢?Java中的数值类型不是对象。可以通过Field类的getDouble
方法,也可以调用get
方法然后进行强制类型转换。反射机制会自动地将这个域值打包到相应的对象包装器中,对于double类型,将打包成Double。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; class Person { private double salary; public Person(double salary) { this.salary = salary; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person(100); Class clz = p.getClass(); Field f = clz.getDeclaredField("salary"); f.setAccessible(true); // double salary = (double) f.get(p); double salary = f.getDouble(p); System.out.println("薪水:" + salary); } }
【运行结果】:
薪水:100.0
利用set方法设置域(属性)值
当然也可以使用set方法,对obj对象的f域设置新值。set方法的签名是:
void set(obj, value) // obj:操作的对象;value:新值
示例:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.Field; class Person { private String name; private double salary; public Person(String name, double salary) { this.name = name; this.salary = salary; } public double getSalary() { return salary; } public String getName() { return name; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Person p = new Person("张三", 100); System.out.println(p.getName() + "-----" + p.getSalary()); Class clz = Person.class; Field f = clz.getDeclaredField("name"); f.setAccessible(true); f.set(p, "萧萧弈寒"); f = clz.getDeclaredField("salary"); f.setAccessible(true); f.set(p, 200); System.out.println(p.getName() + "-----" + p.getSalary()); } }
调用任意方法
invoke方法
与Field类的get方法查看对象的域相似,Method类提供了一个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:
Object invoke(Object obj, Object...args)
第一个参数是隐式参数,其余的对象提供了显示参数。
例如,m1代表Person的getName方法,那么通过invoke
方法获取name的形式为:
String n = m1.invoke(p);
如果返回的是基本类型,invoke方法会返回其包装器类型。例如,m2表示getSalary方法,那么返回的对象实际是一个Double,必须响应的完整类型转换。
double s = m2.invoke(p);
根据Java反射总结(1):解析类结构中的介绍,可以通过Class
类的静态方法getDeclaredMethods
获取Method
对象数组,然后对返回的数组进行遍历,找到指定的方法。与getField
方法类似,getField
方法通过表示域名的字符串,返回一个Field对象。然而,由于方法存在重载的情况,有可能获得若干个相同名字的方法。因此,还必须提供方法的参数类型。getMethod方法的签名:
Method getMethod(String name, Class...paramTypes)
示例:
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class Person { private String name; private double salary; public String getName() { return name; } public double getSalary() { return salary; } public void raiseSalary(double raise) { salary += raise; } public Person(String name, double salary) { this.name = name; this.salary = salary; } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Person p = new Person("萧萧弈寒", 100); // 获取getName方法对象 Method m1 = p.getClass().getMethod("getName"); // 调用getName方法 String name = (String)m1.invoke(p); System.out.println("我的名字是:" + name); // 获取raiseSalary方法对象,需要传double类型的参数 Method m2 = p.getClass().getMethod("raiseSalary", double.class); // 调用raiseSalary方法,并传入参数值 m2.invoke(p, 200); System.out.println("加薪后:salary=" + p.getSalary()); } }
【运行结果】:
我的名字是:萧萧弈寒
加薪后:salary=300.0
对于静态方法,invoke
的第一个参数可以被忽略,既可以将它设置为null。
package com.xiaoxiaoyihan.reflection; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; class Person { public static void speak(String name) { System.out.println("木头!" + name + ",你倒是说话啊!"); } } public class ReflectionDemo2 { public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method m = Person.class.getMethod("speak", String.class); m.invoke(null, "萧萧弈寒"); } }
【运行结果】:
木头!萧萧弈寒,你倒是说话啊!
注意:invoke的参数和返回值必须是Object类型的。这就意味着必须进行多次的类型转换。这样做将会是编译器错过检查代码的机会。此外,通过反射获得方法指针的代码比直接调用方法效率低。