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。