从 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 apples = new ArrayList();

List fruits = apples; // illegal

泛型集合在作为参数时,可以这样定义:

void someMethod(List fruits) {

   for (Fruit f : fruits) {

       System.out.print(f + " ");

   }

   System.out.println();

}

这是合法的,而且可以接收的参数既可以是 List,也可以是 List 或 List。但是在这个方法体中,你只能对集合做“读”操作,而不能向集合添加元素:

   fruits.add(new Apple()); // illegal

很显然,对于参数 fruits 来说,它并不能保证自己一定是 List,因此有这样的限制是可以理解的,虽然感觉很不爽。

如果换成下面这样的定义:

void someMethod(List fruits) {

   fruits.add(new Apple());

}

代码就可以通过编译了,但是你也不能确定 fruits 里面的元素是不是 Fruit 类型的了。

2. 不允许创建泛型集合数组

List[] array = new List[10]; // illegal

3. 类型参数的限制

泛型只是语法层面的,编译后类型参数就不存在了,这称为类型擦除(type erasure),因此 List 其实还是传统的 List,而 List 和 List 的用法只是在代码级别的,编译成字节码后就没有区别了。这样可以尽量保持向后兼容,但是类型参数的价值也大打折扣,比如:

class Foo {

   void doSomething(T param){

       T obj = new T(); // illegal

       T copy = param.clone(); // illegal

   }

}

因为泛型擦除,T在编译后变成了 Object !T extends Cloneable 只是作为编译器保证你写出来的代码符合其约束的条件,运行时就没有了!Object.clone() 方法是受保护的,这里是无法调用的。

想从类型参数那里得到想要的东西是不可能了,必须有其他的途径绕过这样的限制。于是我们会看到很多方法要求提供 Class 或 Class 参数:

public T createInstance(Class cls)  throws Exception {

   return cls.newInstance();

}

4. 泛型接口实现的限制

由于类型擦除,你不能这样实现接口:

interface A {}

class C implements A, A {} // illegal

因为 A 和 A 其实是同一个类!

总结:

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

最后,本文其实是这篇文章的总结:http://www.ibm.com/developerworks/cn/java/j-jtp01255.html