能够分析类能力的程序称为反射。
反射机制可以用来:
1)在运行中分析类的能力。
2)在运行中查看对象,例如,编写一个toString方法供所有类使用。
3)实现通用的数组操作代码。
4)利用Method对象,这个对象很像C++中的函数指针。
。。。。。。
Class类
在程序运行期间,Java运行时系统始终为所有的对象维护一个被成为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
获得Class类对象的三种方式:
1)Employee e ;
Class c1 = e.getClass();
2)String className = “java.util.Date”;
Class c1 = Class.forName(className);
3)类名.class
虚拟机为每个类型管理一个Class对象。因此,可以用==运算符实现两个类对象比较的操作。eg:
if(e.getClass()==Employee.class)…
将forName与newInstance配合使用来创建对象。
String s = “java.util.Date”;
Object m = Class.forName(s).newInstance();
newInstance方法调用默认的构造器(没有参数的构造器),初始化新创建的对象,如果这个类没有默认的构造器,就会抛出一个异常。
如果要以这种方式向希望按名称创建的类构造器提供参数,就不要使用上面那条语句,而必须使用Constructor类中newInstance方法。
利用反射分析类的能力:
在java.lang.reflect包中有三个类Field,Method和Constructor分别用于描述类的域,方法和构造器。这三个类中都有一个叫做getName的方法,用来返回项目的名称。Field类中有个getType方法,用来返回描述域所属类型的Class对象,Method和Constructor类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。这三个类还有一个叫做getModifiers的方法,他将返回一个整型数值,用不同的位开关描述public和static这样的修饰符使用情况。另外,还可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的整型数值。例如,可以使用Modifier类中的isPublic,isPrivate或isFinal判断方法或构造器是否是public,private,final。
Class类中的getFields,getMethod和getConstructors方法将分别返回类提供的public域,方法和构造器,其中包括超类的公有成员。Class类的getDeclareFields,getDeclareMethods和getDeclareConstructors方法将分别返回类中声明的全部域,方法和构造器,其中包括私有和受保护成员,但不包括超类的成员。
Field[] getFileds()
Field[] getDeclaredFields()
getFileds()将返回一个包含Field对象的数组,这些对象记录这个类或其超类的公有域。
getDeclaredFields()也将返回包含Field对象的数组,这些对象记录这个类的全部域。
如果类中没有域,或者Class对象描述的基本类型或数组类型,这些方法将返回一个长度为0的数组。
Method[] getMethods()
Method[] getDeclaredMethods()
返回包含Method对象的数组:getMethods将返回所有的公有方法,包括从超类继承来的公有方法;getDeclaredMethods返回这个类或接口的全部方法,但不包括由超类继承了的方法。
Constructor [] getConstructors()
Constructor [] getDeclaredConstructors()
返回包含Constructor对象的数组,其中包含了Class对象所描述的类的所有公有构造器(getConstructors)或所有构造器(getDeclaredConstructors)。
Employee harry = new Employee("harry",3400);
Class c1 = harry.getClass();
Field f = c1.getDeclaredField("name");
Object v = f.get(harry);
这段代码存在一个问题,由于name域是私有域,所以get方法会抛出一个IllegalAccessException。只有利用get方法才能得到可访问域的值。除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域,而不允许读取他们的值。
反射机制默认行为受限于Java的访问控制,然而,如果一个Java程序没有受到安全管理的控制,就可以覆盖访问控制。为了达到这个目的,需要调用Field,Method或Constructor对象的setAccessible方法:
f.setAccessible(true);
----------
API
java.lang.reflect.AccessibleObject.
void setAccessible(boolean flag)
为反射对象设置可访问标志,true表示屏蔽Java语言的访问检查,使得对象的私有属性也可以被查询和设置。
boolean isAccessible()
返回反射对象的可访问标志的值。
java.lang.Class.
Field getField(String name)
Field getField()
返回指定名称的公有域,或包含所有域的数组。
Field getDeclaredField(String name)
Field [] getDeclaredFields()
返回类中声明的给定名称的域,或者包含声明的全部域的数组。
java.lang.reflect.Field
Object get (Object obj)
返回obj对象中Field对象表示的域值。
void set(Object obj , Object newValue)
用一个新值设置Obj对象中Field对象表示的域。
反射,可以在运行时期动态创建对象;获取对象的属性、方法;
public class Admin {
// Field
private int id = 1000;
private String name = "匿名";
// Constructor
public Admin(){
System.out.println("Admin.Admin()");
}
public Admin(String name){
System.out.println("Admin.Admin()" + name);
}
// Method
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 反射技术
public class App {
// 1. 创建对象
@Test
public void testInfo() throws Exception {
// 类全名
String className = "cn.itcast.c_reflect.Admin";
// 得到类字节码
Class<?> clazz = Class.forName(className);
// 创建对象1: 默认构造函数简写
//Admin admin = (Admin) clazz.newInstance();
// 创建对象2: 通过带参数构造器创建对象
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
Admin admin = (Admin) constructor.newInstance("Jack");
}
@Test
//2. 获取属性名称、值
public void testField() throws Exception {
// 类全名
String className = "cn.itcast.c_reflect.Admin";
// 得到类字节码
Class<?> clazz = Class.forName(className);
// 对象
Admin admin = (Admin) clazz.newInstance();
// 获取所有的属性名称
Field[] fs = clazz.getDeclaredFields();
// 遍历:输出每一个属性名称、值
for (Field f : fs) {
// 设置强制访问
f.setAccessible(true);
// 名称
String name = f.getName();
// 值
Object value = f.get(admin);
System.out.println(name + value);
}
}
@Test
//3. 反射获取方法
public void testMethod() throws Exception {
// 类全名
String className = "cn.itcast.c_reflect.Admin";
// 得到类字节码
Class<?> clazz = Class.forName(className);
// 对象
Admin admin = (Admin) clazz.newInstance();
// 获取方法对象 public int getId() {
Method m = clazz.getDeclaredMethod("getId");
// 调用方法
Object r_value = m.invoke(admin);
System.out.println(r_value);
}
}
调用任意方法:
从表面上看,java没有提供方法指针,即将一个方法的存储地址传给另一个方法,以便第二个方法能够随后调用他。但是,反射机制允许调用任意方法。
为了能够看到方法指针的工作过程,先回忆一下利用Field类的get方法查看对象域的过程,与之类似,在Method方法中有个invoke方法,他允许调用包装在当前Method对象中的方法。
方法签名:不包括返回值类型和修饰符
invoke的方法签名
invoke(Object obj , Object…args)
eg:假设m1代表Employee类的getName方法,
String n = (String) m1.invoke(harry);
如果返回值类型是基本数据类型,invoke方法会返回其包装器类型。
eg:m2表示Employee类的getSalary方法。那么返回的对象实际上是一个Double,必须完成相应的类型转换。可以使用自动拆箱将它转换为一个double:
double s = (Double) m2.invoke(harry);
getMethod的方法签名是:
Method getMethod(String name , Class…parameterTypes)
eg:如何获得Employee类的getName方法和raiseSalary方法的方法指针。
Method m1 = Employee.class.getMethod(“getName”);
Method m2 = Employee.class.getMethod(“raiseSalary”,double.class);
API
public Object invoke(Object implicitParameter,Object[]explicitParameters)
调用这个方法所描述的方法,传递给定参数,并返回方法的返回值。对于静态方法,把NULL最为隐式参数传递。在使用包装器基本类型的值时,基本类型的返回值必须是未包装的。