从 JDK 5开始,Java 增加了对泛型的支持,这是一次比较大的改进,利用好泛型,能够减少代码,提高生产率。但是由于Java一贯的向后兼容原则,让泛型的使用受到了限制,下面我们来研究一下泛型的使用限制。
1. 数组支持协变,泛型集合不支持
abstract class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
Apple[] apples = new Apple[];
Fruit[] fruits = apples; // it works
List<Apple> apples = new ArrayList<Apple>();
List<Fruit> fruits = apples; // illegal
泛型集合在作为参数时,可以这样定义:
void someMethod(List<? extends Fruit> fruits) {
for (Fruit f : fruits) {
System.out.print(f + " ");
}
System.out.println();
}
这是合法的,而且可以接收的参数既可以是 List<Fruit>,也可以是 List<Apple> 或 List<Orange>。但是在这个方法体中,你只能对集合做“读”操作,而不能向集合添加元素:
fruits.add(new Apple()); // illegal
很显然,对于参数 fruits 来说,它并不能保证自己一定是 List<Apple>,因此有这样的限制是可以理解的,虽然感觉很不爽。
如果换成下面这样的定义:
void someMethod(List<? super Apple> fruits) {
fruits.add(new Apple());
}
代码就可以通过编译了,但是你也不能确定 fruits 里面的元素是不是 Fruit 类型的了。
2. 不允许创建泛型集合数组
List<String>[] array = new List<String>[10]; // illegal
3. 类型参数的限制
泛型只是语法层面的,编译后类型参数就不存在了,这称为类型擦除(type erasure),因此 List<T> 其实还是传统的 List,而 List<Apple> 和 List<Orange> 的用法只是在代码级别的,编译成字节码后就没有区别了。这样可以尽量保持向后兼容,但是类型参数的价值也大打折扣,比如:
class Foo<T extends Cloneable> {
void doSomething(T param){
T obj = new T(); // illegal
T copy = param.clone(); // illegal
}
}
因为泛型擦除,T在编译后变成了 Object !T extends Cloneable 只是作为编译器保证你写出来的代码符合其约束的条件,运行时就没有了!Object.clone() 方法是受保护的,这里是无法调用的。
想从类型参数那里得到想要的东西是不可能了,必须有其他的途径绕过这样的限制。于是我们会看到很多方法要求提供 Class<T> 或 Class<?> 参数:
public <T> T createInstance(Class<T> cls) throws Exception {
return cls.newInstance();
}
4. 泛型接口实现的限制
由于类型擦除,你不能这样实现接口:
interface A<T> {}
class C implements A<String>, A<Integer> {} // illegal
因为 A<String> 和 A<Integer> 其实是同一个类!
总结:
泛型从 JDK 5 引进到现在已经很久了,一般情况下我们在使用的时候不会遇到这些问题,但是在更高级的用法上,要小心类型擦除带来的限制!
最后,本文其实是这篇文章的总结: