从 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 引进到现在已经很久了,一般情况下我们在使用的时候不会遇到这些问题,但是在更高级的用法上,要小心类型擦除带来的限制!

最后,本文其实是这篇文章的总结: