泛型的兼容性
因为泛型是在Java SE5的时候引入的,而为了兼容之前没有泛型的代码,而擦除是最好的兼容方法。
擦除的主要问题是将非泛化代码从泛化代码的的转变过程,继续使用,直至客户端准备好用泛型重写这些代码。这个动机不会破坏现有的代码。
擦除的代价也是显著的,泛型不能显示地引用运行时类型的操作之中,例如转型,instanceof
和new
表达式,这是因为所有关于参数的类型信息都丢失了。
所以无论何时,当在编写泛型代码的时候,必须时刻提醒自己,只是看起来具有有关参数的类型信息而已。
如果编写了以下的代码段。
class Foo<T> {
T var;
}
那么,看起来当在创建Foo
的实例时。
Foo<Cat> f = new Foo<Cat>();
class Foo
中的代码应该知道现在工作于Cat
之上,尽管如此,在编写代码的时候,就必须强烈地知道var
只是一个Object
类型。
擦除和迁移兼容性表明,使用泛型不是强制的。
class GenericBase<T> {
private T element;
public T getElement() {
return element;
}
public void setElement(T element) {
this.element = element;
}
}
class Derived1<T> extends GenericBase<T> {
}
class Derived2 extends GenericBase {
}
//class Derived3 extends GenericBase<?> {
// Strange error
//}
public class ErasureAndInheritance {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.getElement();
d2.setElement(obj);// warning here
}
}
上面的代码中,Derived2
继承了GenericBase
,但是没有任何泛型参数,但是编译器没有发出警告。而警告在set()
被调用的时候才出现。
为了关闭警告,Java提供了一个注解(在Java SE5版本前不被支持)
@SuppressWarnings("unchecked")
需要注意的是,这个注解应该尽可能地被放置在可以产生这类警告的方法之上,而不是整个类上。当要关闭警告的时候,最好尽量地"聚焦,这样就不会过于宽泛地关闭警告,而导致意外地屏蔽掉真正的问题。
而Derived3
的错误意味着编译器期待得到的是一个原生基类,而不是一个不确定类型的类。
当希望将类型参数不仅仅当做Object
处理的时候,就需要付出额外的努力去管理边界,并且与C++等语言获得参数化类型相比,需要付出多得多的努力来获得少得回报。这并不是说这些语言比Java更得心应手,而是说它们的参数类型化机制比Java更加强大、更灵活。