擦除的补偿
泛型的擦除丢失了在泛型代码中执行某些操作的能力,所以,在运行时任何需要早知道确切类型信息的操作都将无法工作。
public class Erased<T> {
private final int SIZE = 100;
public void f(Object arg){
// if (arg instanceof T){} // Error
// T var = new T(); // Error
// T[] array = new T[SIZE]; // Error
T[] array = (T[]) new Object[SIZE]; //Unchecked warning
}
}
虽然偶尔可以绕过这些问题来编程,但是有时必须引入类型标签来对擦除进行补偿。这就意味着需要显示地传递Class
对象,以便可以在类型表达式中使用它。
isInstance的补偿
在前面的例子中,对instanceof
进行使用但是最终还是失败了,因为其类型信息已经被擦除。但如果引入类型标签,就可以转而使用动态的isInstance()
class Building {
}
class House extends Building {
}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
// Outputs
true
true
false
true
上面的代码中,编译器将确保类型标签可以匹配泛型参数。
创建类型实例
在之前的代码中,创建一个new T()
的尝试将无法实现,其部分原因是因为擦除,另一部分原因是因为编译器不能验证T
是否含有一个默认(无参)构造器。
Java中的解决方法是传递一个工厂对象,并使用它来创建一个新的实例。而最便捷的工厂对象就是Class
对象,因此如果使用类型标签,那么就可以使用newInstance()
来创建这个类型的新对象。
class ClassAsFactory<T> {
T x;
public ClassAsFactory(Class<T> kind) {
try {
x = kind.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Employee {
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class);
System.out.println("ClassAsFactory<Employee> succeeded");
try {
ClassAsFactory<Integer> fi = new ClassAsFactory<>(Integer.class);
} catch (Exception e) {
System.out.println("ClassAsFactory<Integer> fail");
}
}
}
// Outputs
ClassAsFactory<Employee> succeeded
ClassAsFactory<Integer> fail
上面的代码可以通过编译,但是ClassAsFactory<Integer>
会出现异常而失败,因为Integer
没有任何的默认构造器。而这个错误不是在编译期捕获的,所以一般不赞成使用这样的方法。而是通过使用显示的工厂,并将限制其类型,是的只能接受实现了这个工厂方法的类。
interface FactoryI<T> {
T create();
}
class Foo2<T> {
private T x;
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create();
}
}
class IntegerFactory implements FactoryI<Integer> {
@Override
public Integer create() {
return new Integer(0);
}
}
class Widget{
public static class Factory implements FactoryI<Widget>{
@Override
public Widget create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
上面的代码中通过工厂对象来创键对象,需要注意的是。这是Class<T>
的一种变体。这两种方式其实都传递了工厂对象,而Class<T>
其实也是内建的工厂对象,上面的方法创建了一个显式的工厂对象,但是却可以获得编译期检查。
另一种补偿的方式是模板方法设计模式,在下面的代码中,get()
是模板方法,而create()
是在子类中定义的,用来产生子类型的对象。
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() {
element = create();
}
protected abstract T create();
}
class X {
}
class Creator extends GenericWithCreate<X> {
@Override
protected X create() {
return new X();
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator creator = new Creator();
creator.f();
}
}
// Outputs
X