Java教程是针对JDK 8编写的。本页面中描述的示例和实践不利用后续版本引入的改进,并可能使用不再可用的技术。
请参阅Java语言更改,了解Java SE 9及后续版本中更新的语言功能摘要。
请参阅JDK发行说明,了解所有JDK版本的新功能、增强功能以及已删除或不推荐使用的选项。
本节类型擦除讨论编译器删除与类型参数和类型参数相关的信息的过程。类型擦除对于具有非可重化类型的变量参数(也称为可变参数)方法有影响。有关可变参数方法的更多信息,请参见方法或构造函数中传递信息中的任意数量的参数部分。
本页包括以下主题:
可重化类型是一种在运行时完全可用的类型信息的类型。这包括原始类型、非泛型类型、原始类型和未绑定通配符的调用。
非可重化类型是在编译时通过类型擦除删除了信息的类型 — 未定义为无限制通配符的泛型类型调用。非可重化类型在运行时没有所有信息可用。非可重化类型的示例是List<String>和List<Number>;JVM在运行时无法区分这些类型之间的差异。正如泛型限制中所示,有些情况下无法使用非可重化类型:例如,在instanceof表达式中或作为数组中的元素。
堆污染发生在参数化类型的变量引用一个不属于该参数化类型的对象时。如果程序执行了某些操作,在编译时会出现未经检查的警告。如果无法在编译时(在编译时类型检查规则的限制范围内)或运行时验证涉及参数化类型的操作(例如转换或方法调用)的正确性,将生成未经检查的警告。例如,当混合使用原始类型和参数化类型,或进行未经检查的转换时,会发生堆污染。
在正常情况下,当所有代码同时编译时,编译器会发出未经检查的警告,以引起您对潜在堆污染的注意。如果您将代码分开编译,很难检测到潜在的堆污染风险。如果确保代码在没有警告的情况下编译,那么就不会发生堆污染。
包含可变参数输入参数的通用方法可能导致堆污染。
考虑以下ArrayBuilder类:
public class ArrayBuilder { public static <T> void addToList (List<T> listArg, T... elements) { for (T x : elements) { listArg.add(x); } } public static void faultyMethod(List<String>... l) { Object[] objectArray = l; // Valid objectArray[0] = Arrays.asList(42); String s = l[0].get(0); // 在此处抛出ClassCastException } }
下面的例子HeapPollutionExample使用了ArrayBuiler类:
public class HeapPollutionExample { public static void main(String[] args) { List<String> stringListA = new ArrayList<String>(); List<String> stringListB = new ArrayList<String>(); ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine"); ArrayBuilder.addToList(stringListB, "Ten", "Eleven", "Twelve"); List<List<String>> listOfStringLists = new ArrayList<List<String>>(); ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB); ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!")); } }
编译时,通过ArrayBuilder.addToList方法的定义会产生以下警告:
warning: [varargs] Possible heap pollution from parameterized vararg type T
当编译器遇到可变参数方法时,它将可变参数形式参数转换为数组。然而,Java编程语言不允许创建参数化类型的数组。在方法ArrayBuilder.addToList
中,编译器将可变参数形式参数T... elements
转换为形式参数T[] elements
,即一个数组。然而,由于类型擦除,编译器将可变参数形式参数转换为Object[] elements
。因此,存在堆污染的可能性。
下面的语句将可变参数形式参数l
赋值给Object
数组objectArray
:
Object[] objectArray = l;
这个语句有可能引入堆污染。一个不匹配可变参数形式参数l
的参数化类型的值可以被赋值给变量objectArray
,从而可以被赋值给l
。然而,编译器在这个语句上不会生成未经检查的警告。编译器在将可变参数形式参数List<String>... l
转换为形式参数List[] l
时已经生成了警告。这个语句是有效的;变量l
的类型是List[]
,它是Object[]
的子类型。
因此,如果你将任何类型的List
对象赋值给objectArray
数组的任何数组元素,编译器不会发出警告或错误,就像这个语句所示:
objectArray[0] = Arrays.asList(42);
这个语句将一个包含一个Integer
类型对象的List
对象赋值给objectArray
数组的第一个数组元素。
假设你使用以下语句调用ArrayBuilder.faultyMethod
:
ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
在运行时,JVM在以下语句处抛出一个ClassCastException
:
// ClassCastException thrown here String s = l[0].get(0);
存储在变量l
的第一个数组元素中的对象的类型是List<Integer>
,但是这个语句期望的是一个List<String>
类型的对象。
如果你声明了一个具有参数化类型参数的可变参数方法,并且确保方法体不会由于对可变参数形式参数的不正确处理而抛出ClassCastException
或其他类似异常,你可以在静态和非构造方法声明中添加以下注解来防止编译器为这些类型的可变参数方法生成的警告:
@SafeVarargs
@SafeVarargs
注解是方法合同的一部分;这个注解断言方法的实现不会不正确地处理可变参数形式参数。
也可以通过在方法声明中添加以下内容来抑制此类警告,但这种方法不太理想:
@SuppressWarnings({"unchecked", "varargs"})
然而,这种方法无法抑制从方法的调用处生成的警告。如果你对@SuppressWarnings
语法不熟悉,请参阅注解。