何为泛型擦除
java中实现的泛型是伪泛型:即对于泛型graph<Integer>和graph<Double>,在编译后都会变成graph,在JVM看来没有区别。
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为泛型擦除。
究其原因,是因为泛型并不是java一开始就存在的特性,而是在1.5版本后附加的新特性,考虑到之前库与代码的兼容性,只能采取可擦除这一机制才能保证既实现了泛型的概念,又保证原先代码的可用性,是一种无奈之举。
数组与泛型容器
java中用来储存多个对象常用的手段便是数组和泛型容器,再考虑泛型擦除的影响之前,我们先来探讨一下数组与容器的不同:
数组——共变数组
为保证没有泛型之前的一些用数组解决的问题,需要数组能像其元素类型一样能发生协变,我们称之为数组的协变性:
- 例如:Base是Sub的基类,那么Base[]便是Sub[]的基类。
由于这个特性,我们经常对数组进行转型操作,而正是这一转型操作,足以骗过编译器,造成一些不易察觉的错误,也使得java不得不放弃泛型数组。
泛型——不存在协变
- 例如 Base是Sub的基类,但是List<Base>不是List<Sub>的基类。
这一不支持协变的特性使得在编译器检查阶段能确保形式的正确,在擦除后也能正常工作。
泛型数组 < T >
java并不是静止泛型数组的存在,而是禁止直接创建泛型数组实例的操作:
List<Integer>[] myList = new List<Integer>[10];
//error Cannot create a generic array of List<Integer>
试想,如果支持这种操作会有什么后果?
引用sun文档的例子
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
假设第一行的泛型数组没有加以限制,通过向上转型后便可插入其他类型的泛型元素,在插入时因为泛型擦除不会发生错误,而取出时将导致运行出错,为了防止这种类型隐式转换造成的问题,java声明泛型数组必须显式的进行转换。
泛型数组 < ? >
java要求,声明泛型数组必须采用通配符:
List<?>[] lsa = new List<?>[10]; //1
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
lsa[1] = li;
// Run time error, but cast is explicit.
Integer s = (Integer) lsa[1].get(0); //2
这种做法需要我们清晰的清楚泛型数组中的元素类型,并加以显式的类型转换。
总结
综上,数组的类型不可以是类型变量,除非是采用通配符的方式。因为对于通配符的方式,最后取出数据是要做显式的类型转换的。而List<>等泛型实现的容器没有协变性,可以在编译检查解决大部分错误,是更加省心省力的选择。