Java 教程是为 JDK 8 编写的。本页中描述的示例和实践未利用在后续版本中引入的改进。
Type Erasure 部分讨论了编译器移除与类型形参和类型实参相关的信息的过程。类型擦除具有与可变参数(也称为 varargs)方法相关的后果,其 varargs 形式参数具有不可具体化类型。有关 varargs 方法的更多信息,请参阅 Passing Information to a Method or a Constructor 中的 Arbitrary Number of Arguments 部分。
此页面包含以下主题:
reifiable (可具体化的) 类型是类型信息在运行时完全可用的类型。这包括基本类型,非泛型类型,原始类型和无界通配符的调用。
Non-reifiable types (不可具体化的类型) 是在编译时通过类型擦除移除信息的类型 - 未定义为无界通配符的泛型类型的调用。不可具体化的类型在运行时没有提供所有信息。不可具体化的类型的示例是 List<String> 和 List<Number>; JVM 无法在运行时区分这些类型。如 Restrictions on Generics 所示,在某些情况下,不能使用不可具体化类型:例如,在 instanceof 表达式中,或作为数组中的元素。
Heap pollution (堆污染)发生于参数化类型的变量引用的是不属于该参数化类型的对象时。如果程序执行某些操作,在编译时产生未经检查的警告,则会出现这种情况。如果在编译时(在编译时类型检查规则的限制内)或在运行时,涉及参数化类型的操作(例如,类型转换或方法调用)的正确性无法验证时,则会生成 unchecked warning (未经检查的警告)。例如,在混合原始类型和参数化类型时,或者在执行未经检查的强制转换时,会发生堆污染。
在正常情况下,当所有代码同时编译时,编译器会生成未经检查的警告,以引起你对潜在堆污染的注意。如果单独编译代码的各个部分,则很难检测到堆污染的潜在风险。如果确保代码在没有警告的情况下编译,则不会发生堆污染。
包含 vararg 输入参数的泛型方法可能会导致堆污染。
请考虑以下 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 thrown here
}
}
以下示例 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
当编译器遇到 varargs 方法时,它会将 varargs 形式参数转换为数组。但是,Java 编程语言不允许创建参数化类型的数组。在方法 ArrayBuilder.addToList 中,编译器将 varargs 形式参数 T... elements 转换为形式参数 T[] elements,一个数组。但是,由于类型擦除,编译器会将 varargs 形式参数转换为 Object[] elements。因此,存在堆污染的可能性。
以下语句将 varargs 形式参数 l 分配给 Object 数组 objectArgs:
Object[] objectArray = l;
该语句可能会引入堆污染。与 varargs 形式参数 l 的参数化类型匹配的值可以分配给变量 objectArray,因此可以分配给 l 。但是,编译器不会在此语句中生成未经检查的警告。编译器在将 varargs 形式参数 List<String>... l 转换为形式参数 List[] l 时已生成警告。本语句有效;变量 l 的类型为 List[],它是 Object[] 的子类型。
因此,如果将任何类型的 List 对象分配给 objectArray 数组的任何数组组件,编译器不会发出警告或错误,如下所示:
objectArray[0] = Arrays.asList(42);
此语句将 List 对象分配给 objectArray 数组的第一个数组组件,该对象包含一个类型为 Integer 的对象。
假设你使用以下语句调用 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>。
如果声明一个包含参数化类型的参数的 varargs 方法,并确保方法体不会因 varargs 形式参数处理不当而抛出 ClassCastException 或其他类似异常,那么通过向静态和非构造方法声明添加以下注解,可以避免编译器为这些类型的 varargs 方法生成警告:
@SafeVarargs
@SafeVarargs 注解是方法合同的文档部分;这个注解断言该方法的实现不会不正确地处理 varargs 形式参数。
尽管不太理想,但通过在方法声明中添加以下内容来抑制此类警告也是可能的:
@SuppressWarnings({"unchecked", "varargs"})
但是,此方法不会抑制从方法的调用处生成的警告。如果你不熟悉 @SuppressWarnings 语法,请参阅 Annotations。