文档

Java™ 教程-Java Tutorials 中文版
不可具体化的类型
Trail: Learning the Java Language
Lesson: Generics (Updated)
Section: Type Erasure

不可具体化的类型

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 (未经检查的警告)。例如,在混合原始类型和参数化类型时,或者在执行未经检查的强制转换时,会发生堆污染。

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

具有不可具体化的形式参数的 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 。但是,编译器不会在此语句中生成未经检查的警告。编译器在将 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 注解是方法合同的文档部分;这个注解断言该方法的实现不会不正确地处理 varargs 形式参数。

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

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

但是,此方法不会抑制从方法的调用处生成的警告。如果你不熟悉 @SuppressWarnings 语法,请参阅 Annotations


Previous page: Effects of Type Erasure and Bridge Methods
Next page: Restrictions on Generics