ArrayList<String>() 和 ArrayList<Integer>() 很容易被认为是不同的类型,但是下面的打印结果却是 true:
public class ErasedType { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); // output:true System.out.println(c1 == c2); } }分别打印它们的参数类型,可以发现,无论指定的是 Integer 类型还是 String 类型,最后输出结果都仅是一个 用作参数占位符的标识符 [E] 而已:
// output:[E] System.out.println(Arrays.toString(c1.getTypeParameters())); // output:[E] System.out.println(Arrays.toString(c2.getTypeParameters()));这意味着,在使用泛型时,任何具体的类型信息,比如上例中的 Integer 或 String,在泛型内部都是无法获得的,也就是,被擦除了。唯一知道的,就只是正在使用着的对象。由于 ArrayList<String>() 和 ArrayList<Integer>() 都会被擦除成“原生态”(也即 List),因此,在运行时都算作是相同的类型。
由于,
在泛型代码内部,无法获得任何有关泛型参数类型的信息 ——《Java 编程思想》
程序中运行的所有泛型类型都将被擦除,替换为它们的非泛型上界,例如,List<T> 会被擦除为 List。对于普通的类型参数,如果指定了边界,例如<T extends Integer>,类型参数会被擦除为边界(Integer),如果未指定边界,例如 <T> ,类型参数会被擦除为 Object 。
由于泛型擦除,泛型不能用于显示地引用运行时类型的操作之中,例如转型、instanceof 和 new 表达式,因为所需要在运行时需要知道的关于参数的类型信息(也就是 <T>中的 T)都会丢失。
什么时候会发生堆污染呢?
当参数化类型的变量指向一个非参数化类型的对象时,会发生堆污染。如果程序在执行某些操作,在编译时产生 unchecked warning (未经检查的警告),就会出现这种情况。无论是 编译时 还是 运行时 ,如果无法验证一个涉及参数化的类型的操作(例如:类型强转、方法调用)是否正确,就会产生一个 unchecked warning 。
也就是说,当一个参数化的类型无法再编译或运行时被确定,会产生一个 unchecked warning ,这时,就发生了堆污染。
前面有提到,由于泛型擦除,泛型是不能显示的完成转型、instanceof 和 new 表达式 的操作的。比如下面的例子:
// 语句 1 List list = new ArrayList<Integer>(); // 语句 2 -- unchecked warning List<String> strList = list;由于语句 1 声明了一个无泛型的对象 list(实际存储的是Integer类型的参数),语句 2 将无泛型的 list 对象,赋给了 String 类型 的泛型变量 strList,此处会发生堆污染。为什么呢?
由于泛型擦除,new ArrayList<Integer>() 和 List<String> strList 会被擦除为 ArrayList 和 List,当 list 指向 strList 时,编译器是无法确定 list 和 strList 的参数类型的,因为在运行时 list 的类型信息 “Integer” 和 strList 的类型信息 “String” 都已经被擦除了,这时就会产生 unchecked warning ,自然就会发生 堆污染了。
在正常情况下,当所有代码同时编译时,编译器会发出未经检查的警告,以引起对潜在堆污染的注意。如果单独编译代码的各个部分,则很难检测到堆污染的潜在风险。如果确保代码在没有警告的情况下编译,则不会发生堆污染。