“ 类型擦除”部分讨论了编译器删除与类型参数和类型参数有关的信息的过程。类型擦除的结果与变量参数(也称为varargs)方法有关,这些方法的varargs形式参数具有不可更改的类型。请参见信息传递给方法或构造中的任意数量的参数,了解更多有关可变参数的方法。
此页面涵盖以下主题:
- 不可具体化的类型
- 堆污染
- 具有不可具体化形式参数的Varargs方法的潜在漏洞
- 防止使用不可具体化形式参数的Varargs方法发出警告
不可具体化的类型
可__具体化类型(reifiable type)是其类型信息在运行时完全可用的类型。这包括基本类型,非泛型类型,原始类型以及无界通配符的调用。
不可具体化类型(Non-reifiable types)是指在编译时通过类型擦除删除了信息的类型-泛型类型的调用未定义为无界通配符。不可具体化类型在运行时并不具有所有可用信息。不可具体化类型的示例是List和List ; JVM在运行时无法分辨出这些类型之间的区别。如泛型限制所示,存在不可具体化类型不能使用的某些情况:例如在一个instanceof表达,或者作为在数组的元素。 堆污染
当参数化类型的变量引用的对象不是该参数化类型的对象时,就会发生堆污染(**Heap pollution**)。如果程序执行某些操作会在编译时产生unchecked警告,则会发生这种情况。如果在编译时(在编译时,检查类型规则的范围内)或在运行时,无法确定涉及参数化类型的操作(例如,强制转换或方法调用)的正确性,则会生成unchecked警告。 例如,当混合原始类型和参数化类型时,或者执行未经检查的强制转换时,就会发生堆污染。
在正常情况下,当同时编译所有代码时,编译器会发出未经检查的警告,以引起您对潜在堆污染的注意。如果分别编译代码部分,则很难检测到堆污染的潜在风险。如果确保代码在没有警告的情况下进行编译,则不会发生堆污染。具有不可具体化形式参数的Varargs方法的潜在漏洞
包含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
。 但是,编译器不会在此语句上生成unchecked警告。 当编译器将varargs形式参数List<String>... l
转换为形式参数List[] l
时,已经生成了警告。 此声明有效; 变量l
具有类型List[]
,它是Object[]
的子类型。
因此,如果将任何类型的List
对象分配给objectArray
数组的任何数组组件,则编译器不会发出警告或错误,如以下语句所示:
objectArray[0] = Arrays.asList(42);
该语句将List
对象分配给objectArray
数组的第一个数组组件,该List
对象包含一个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方法,并确保由于对varargs形式参数的处理不当,该方法的主体不会引发ClassCastException
或其他类似异常,则可以通过将以下注解添加到静态和非构造器的方法声明中,防止编译器生成警告,以用于此类varargs方法:
@SafeVarargs
@SafeVarargs
注解是方法的协议的文件部分; 该注解断言方法的实现不会不适当地处理varargs形式参数。
尽管不太理想,但也可以通过在方法声明中添加以下内容来禁止此类警告:
@SuppressWarnings({"unchecked", "varargs"})
但是,这种方法不能禁止从该方法的调用站点生成的警告。如果您不熟悉@SuppressWarnings
语法,请参阅 注解。