Non-Reifiable Types

Type Erasure部分讨论了编译器删除与类型参数和类型参数有关的信息的过程。类型擦除的结果与变量参数(也称为* varargs *)方法有关,这些方法的 varargs 形式参数具有不可修改的类型。有关 varargs 方法的更多信息,请参见将信息传递给方法或构造函数中的任意参数部分。

此页面包含以下主题:

Non-Reifiable Types

  • reifiable *类型是其类型信息在运行时完全可用的类型。这包括基元,非泛型类型,原始类型和未绑定通配符的调用。

非可修正类型是在编译时通过类型擦除删除了信息的类型-泛型类型的调用未定义为无界通配符。不可修改的类型在运行时并不具有所有可用信息。不可修饰类型的示例是List\<String\>List\<Number\>; JVM 在运行时无法分辨出这些类型之间的区别。如泛型限制所示,在某些情况下无法使用不可调整类型:例如,在instanceof表达式中或在数组中作为元素。

Heap Pollution

当参数化类型的变量引用的对象不是该参数化类型的对象时,就会发生“堆污染”。如果程序执行某些操作会在编译时产生未经检查的警告,则会发生这种情况。如果在编译时(在编译时类型检查规则的范围内)或在运行时,涉及参数化类型的操作的正确性(例如,强制类型转换或方法调用),则会生成“未经检查的警告”无法验证。例如,当混合原始类型和参数化类型时,或者执行未经检查的强制转换时,就会发生堆污染。

在正常情况下,当同时编译所有代码时,编译器会发出未经检查的警告,以引起您对潜在堆污染的注意。如果分别编译代码段,则很难检测到堆污染的潜在风险。如果您确保代码在没有警告的情况下进行编译,则不会发生堆污染。

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

包含 varargImporting 参数的泛型方法可能导致堆污染。

考虑以下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 方法,并确保由于对 varargs 形式参数的处理不当,该方法的主体不会引发ClassCastException或其他类似的异常,则可以避免警告编译器通过将以下 注解 添加到静态和非构造方法声明中,为此类 varargs 方法生成:

@SafeVarargs

@SafeVarargs注解是该方法 Contract 中记录在案的部分;该 注解assert 该方法的实现不会不适当地处理 varargs 形式参数。

尽管不太理想,但也可以通过在方法声明中添加以下内容来抑制此类警告:

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

但是,这种方法不能抑制从该方法的调用站点生成的警告。如果您不熟悉@SuppressWarnings语法,请参见Annotations