类型擦除”部分讨论了编译器删除与类型参数和类型参数有关的信息的过程。类型擦除的结果与变量参数(也称为varargs)方法有关,这些方法的varargs形式参数具有不可更改的类型。请参见信息传递给方法或构造中的任意数量的参数,了解更多有关可变参数的方法。
此页面涵盖以下主题:

  • 不可具体化的类型
  • 堆污染
  • 具有不可具体化形式参数的Varargs方法的潜在漏洞
  • 防止使用不可具体化形式参数的Varargs方法发出警告

    不可具体化的类型

    可__具体化类型(reifiable type)是其类型信息在运行时完全可用的类型。这包括基本类型,非泛型类型,原始类型以及无界通配符的调用。
    不可具体化类型(Non-reifiable types是指在编译时通过类型擦除删除了信息的类型-泛型类型的调用未定义为无界通配符。不可具体化类型在运行时并不具有所有可用信息。不可具体化类型的示例是List 和List ; JVM在运行时无法分辨出这些类型之间的区别。如泛型限制所示,存在不可具体化类型不能使用的某些情况:例如在一个instanceof表达,或者作为在数组的元素。

    堆污染

    当参数化类型的变量引用的对象不是该参数化类型的对象时,就会发生堆污染(**Heap pollution**。如果程序执行某些操作会在编译时产生unchecked警告,则会发生这种情况。如果在编译时(在编译时,检查类型规则的范围内)或在运行时,无法确定涉及参数化类型的操作(例如,强制转换或方法调用)的正确性,则会生成unchecked警告。 例如,当混合原始类型和参数化类型时,或者执行未经检查的强制转换时,就会发生堆污染。
    在正常情况下,当同时编译所有代码时,编译器会发出未经检查的警告,以引起您对潜在堆污染的注意。如果分别编译代码部分,则很难检测到堆污染的潜在风险。如果确保代码在没有警告的情况下进行编译,则不会发生堆污染。

    具有不可具体化形式参数的Varargs方法的潜在漏洞

    包含vararg输入参数的泛型方法可能导致堆污染。
    考虑以下ArrayBuilder类:
  1. public class ArrayBuilder {
  2. public static <T> void addToList (List<T> listArg, T... elements) {
  3. for (T x : elements) {
  4. listArg.add(x);
  5. }
  6. }
  7. public static void faultyMethod(List<String>... l) {
  8. Object[] objectArray = l; // Valid
  9. objectArray[0] = Arrays.asList(42);
  10. String s = l[0].get(0); // ClassCastException thrown here
  11. }
  12. }

以下示例HeapPollutionExample使用ArrayBuiler类:

  1. public class HeapPollutionExample {
  2. public static void main(String[] args) {
  3. List<String> stringListA = new ArrayList<String>();
  4. List<String> stringListB = new ArrayList<String>();
  5. ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
  6. ArrayBuilder.addToList(stringListB, "Ten", "Eleven", "Twelve");
  7. List<List<String>> listOfStringLists =
  8. new ArrayList<List<String>>();
  9. ArrayBuilder.addToList(listOfStringLists,
  10. stringListA, stringListB);
  11. ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
  12. }
  13. }

编译后,ArrayBuilder.addToList方法的定义会产生以下警告:

  1. 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

  1. Object[] objectArray = l;

该语句可能会导致堆污染。 可以将与varargs形式参数l的参数化类型匹配的值分配给变量objectArray,从而可以将其分配给l。 但是,编译器不会在此语句上生成unchecked警告。 当编译器将varargs形式参数List<String>... l转换为形式参数List[] l时,已经生成了警告。 此声明有效; 变量l具有类型List[],它是Object[]的子类型。
因此,如果将任何类型的List对象分配给objectArray数组的任何数组组件,则编译器不会发出警告或错误,如以下语句所示:

  1. objectArray[0] = Arrays.asList(42);

该语句将List对象分配给objectArray数组的第一个数组组件,该List对象包含一个Integer类型的对象。
假设您使用以下语句调用ArrayBuilder.faultyMethod

  1. ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));

在运行时,JVM在以下语句中抛出ClassCastException

  1. // ClassCastException thrown here
  2. String s = l[0].get(0);

存储在变量的第一个数组组件中的对象l具有类型List<Integer>,但是此语句期望使用类型为List<String>的对象。

防止使用不可具体化形式参数的Varargs方法发出警告

如果您声明具有参数化类型参数的varargs方法,并确保由于对varargs形式参数的处理不当,该方法的主体不会引发ClassCastException或其他类似异常,则可以通过将以下注解添加到静态和非构造器的方法声明中,防止编译器生成警告,以用于此类varargs方法:

  1. @SafeVarargs

@SafeVarargs注解是方法的协议的文件部分; 该注解断言方法的实现不会不适当地处理varargs形式参数。
尽管不太理想,但也可以通过在方法声明中添加以下内容来禁止此类警告:

  1. @SuppressWarnings({"unchecked", "varargs"})

但是,这种方法不能禁止从该方法的调用站点生成的警告。如果您不熟悉@SuppressWarnings语法,请参阅 注解