Java的反射非常强大,它能访问对象的私有field、method,私有构造器,甚至私有的内部类和匿名内部类。反射就像是Java提供的后门,对OOP来说它提供了一种非安全的访问方式。它是一种特殊手段,一般情况下我们不使用,因为反射可能非安全地修改对象数据,需要写更多的捕获异常代码,代码可读性差些。另外能通过反射来操作类对象,该类的.class字节码文件必须首先被虚拟机加载。
下面这个例子,来源于《Java编程思想》P667“Generics”一章的习题22,题目大意就是通过已知的类标签(Class<?>)、反射来构造该类的一个实例,可以调用该类的带参数构造函数。这个例子中除了反射,还用到了jdk1.5开始提供的泛型和可变参数。
我们先来了解下泛型,Java中的泛型,最初是为在容器对象中的元素提供编译时的类型安全检查(bug越早发现越好),实际通过利用泛型能写出更通用的方法,减少重复代码;但是Java为了向之前版本的代码兼容,所以在运行时擦除了泛型信息,因此它的泛型没有C++的彻底。由于运行时不保留类型信息,所以我们在这个例子中加入一个Class kind成员属性来保存泛型的实际类型信息。可变参数一般在反射时用到,就是方法在该参数位置上能接受0个,一个,或任意多个参数;实际上Java会对传入的可变参数,生成一个数组,当一个参数都没有时,这个数组就是null,因此过程会多出一个创建数组的开销,这也是一般代码中不用的原因。
在这个做完这个习题后,发现除了通过反射调用带参的构造函数来创建对象,还能自动选择构造器,做了进一步的修改。通过调用create方法,传递构造器所需要的参数,就能找到对应的构造器执行实例化。当构造失败或者找不到对应的构造器时,就会返回null(可用“NULL对象模式”)。再改改差不多可以当做一个工具类来用了。
注意:House类的构造函数参数都是包装类,调用ojb = ctt.create(30);会对基本型int进行包装为Integer,所以能找到构造函数public House (Integer age);如果House类的构造函数参数是基本类型int,调用kind.getDeclaredConstructor(parameterTypes)方法会找不到构造函数抛出NoSuchMethodException,这种情况可以参照apache的一个工具类的org.apache.commons.lang.ClassUtils.wrapperToPrimitive(Class cls)方法,再次进行尝试。
/**
* @author South Park
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
class Building {}
class House extends Building {
private Integer age = 20;
private String name = "default name";
private String address = "default address";
public House() {}
public House (Integer age) {
this.age = age;
}
public House (Integer age, String name) {
this.age = age;
this.name = name;
}
public House (Integer age, String name, String address) {
this.age = age;
this.name = name;
this.address = address;
}
public House (String name, Integer age) {
this.age = age;
this.name = name + "(2)";
}
private House (String name, String address) {//私有构造器
this.address = address;
this.name = name;
}
public String toString() {
return "age:"+age+", name:"+name+", address:"+address;
}
}
public class ClassTypeCapture<t> {
Class<t> kind;
public ClassTypeCapture(Class<t> kind) {
this.kind = kind;
}
public <w extends="" object=""> T create(W... w) {
try {
if (w != null && w.length > 0) {
Class<?>[] parameterTypes = new Class<?>[w.length];
int index =0;
for (W arg : w) {
parameterTypes[index++] = arg.getClass();
}
Constructor<t> constructor = kind.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);//让私有构造函数也可以调用
return constructor.newInstance(w);
} else {
return kind.newInstance();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
public boolean isInstance(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<house> ctt = new ClassTypeCapture<house>(House.class);
House ojb = ctt.create();
System.out.println(ojb);
ojb = ctt.create(30);
System.out.println(ojb);
ojb = ctt.create(new Integer(31), "Star City");
System.out.println(ojb);
ojb = ctt.create(new Integer(32), "Star City", "JiuXianQiao");
System.out.println(ojb);
System.out.println("===================================================");
ojb = ctt.create("South Park", 41);
System.out.println(ojb);
ojb = ctt.create(41, "South Park");
System.out.println(ojb);
ojb = ctt.create("my name", "my street");
System.out.println(ojb);
}
} /* Output:
age:20, name:default name, address:default address
age:30, name:default name, address:default address
age:31, name:Star City, address:default address
age:32, name:Star City, address:JiuXianQiao
===================================================
age:41, name:South Park(2), address:default address
age:41, name:South Park, address:default address
age:20, name:my name, address:my street
*/