当前位置: 首页 > 技术随笔 > Java SE 7新特性:改进使用带泛型可变参数的方法时的编译器警告和错误提示

Java SE 7新特性:改进使用带泛型可变参数的方法时的编译器警告和错误提示

堆污染

大部分参数化类型,例如ArrayList<Number>List<String>,都属于非具体化类型(non-reifiable types)。非具体化类型是指在运行时(runtime)并不完整的类型。在编译时,非具体化类型经过了一个名为「类型擦除」的过程,编译器删除了与类型参数相关的信息。这将保证Java运行库与那些诞生在Java泛型之前的应用程序之间的二进制兼容性。由于在编译时,类型擦除操作删除了来自参数化类型的相关信息,因此它们是不完整的。

一个参数化类型的变量引用一个非参数化类型的对象,将会导致堆污染。这种情况只会在程序执行了一些在编译时会出现未经检查的警告(unchecked warning)的操作时发生。不管是在编译时(符合编译时类型检查规则的约束),还是在运行时,如果无法验证一个表示参数化类型的操作(例如:类型强转、方法调用)是否正确,就会产生一个未经检查的警告。

先看下面一段代码:

List l = new ArrayList<Number>();
List<String> ls = l;       // unchecked warning
l.add(0, new Integer(42)); // another unchecked warning
String s = ls.get(0);      // ClassCastException is thrown

在类型擦除的时候,类型ArrayList<Number>List<String>将会分别变为ArrayListList。 变量ls具有参数化类型List<String>。当变量l引用的类型List被分配给变量ls时,编译器将会产生一个未经检查的警告;编译器无法确定,而且我们也知道Java虚拟机(JVM) 在运行时也无法确定变量l是否引用了类型List<String>,于是堆污染就产生了。

因此,在编译时,编译器在add语句处生成了又一个未经检查的警告。编译器无法确定变量l引用的类型是List<String>还是List<Integer>(或者其他可能产生堆污染的情况)。

不过,编译器不会在get语句处生成一个警告或错误。这个语句是有效的;程序将调用List<String>.get方法并返回一个字符串对象。不过,在运行时,get语句将会抛出一个类型转换异常java.lang.ClassCastException

更详细地说,堆污染发生在静态类型为List<Number>的List对象l被分配给另一个具有不同静态类型List<String>的List对象ls时。不过编译器仍然允许该分配,以便于向后兼容那些不支持泛型的Java SE 版本。由于类型擦除的缘故,类型List<Number>List<String>都会变为List。这样,编译器允许对象l以原始类型List的方式被分配给对象ls

此外,堆污染还发生在l.add方法被调用时。add方法的第二个形式参数的静态类型是String,但该方法被调用时使用的却是一个不同类型(Integer)的实际参数。不过,编译器仍然允许其通过。由于类型擦除的缘故,add方法的第二个形式参数的类型变成了Object,这里的add被定义为List<E>.add(int, E),而Object是Number和String的公共父类。在类型擦除之后,l.add可以添加一个类型为Object的对象,当然也包括它的子类Integer,所以编译器允许其通过编译。

可变参数方法和非具体化的形式参数

我们先来看下面例子中的ArrayBuilder.addToList方法,它是一个可变参数方法,作用是将可变参数elements中类型为T的对象添加到形式参数List listArg中。

import java.util.*;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(new Integer(42));
    String s = l[0].get(0);    // ClassCastException thrown here
  }}import java.util.*;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(stringListA, "Ten", "Eleven", "Twelve");
    List<List<String>> listOfStringLists = new ArrayList<List<String>>();
    ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);

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

Java SE 7 的编译器将会在ArrayBuilder.addToList方法的定义出产生如下警告:

warning: [varargs] Possible heap pollution from parameterized vararg type T

当遇到可变参数的方法时,编译器会将可变参数形式的形式参数转化为一个数组。但是,Java编程语言并不允许创建泛型数组。在方法ArrayBuilder.addToList中,编译器将可变参数形式的形式参数T... elements转化为一个数组T[] elements。但是由于类型擦除的缘故,编译器会将其转换为Object[] elements,这时,堆污染将可能产生。

注意:ArrayBuilder.addToList方法被调用时,Java SE 5和Java SE 6的编译器将会产生一个警告,这个警告是针对类HeapPollutionExample的;它们不会在声明的时候产生一个警告,而Java SE 7在声明和调用时都会产生一个警告(除非你使用了注解@SuppressWarnings忽略了该警告)。当编译器遇到一个带有非具体化可变参数的形式参数的方法时,相对于在方法调用时(产生警告)而言,在方法声明时产生警告的好处在于方法的声明只有一个,而方法调用的地方可能有多个。

方法ArrayBuilder.faultyMethod展示了对于这些方法为什么编译器会给出警告。方法中的第一条语句将可变参数l分配给了Object数组objectArgs:

Object[] objectArray = l;

这条语句可能引入堆污染,一个与可变参数l的参数化类型相匹配的值可以被赋给变量objectArray,从而也赋给变量l。不过,编译器并不会在这条语句处产生一个未经检查的警告, 因为在将可变参数形式的形式参数List<String>... l转化为形式参数List[] l时,编译器已经产生了一个警告。这条语句是有效的,变量l具有Object[]的子类型List[]。

因此,正如下面这条语句所示,如果你将一个任何类型的List对象赋值给objectArray数组中的任何元素,编译器并不会发出一个错误或警告:

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

这条语句将一个Integer类型的List对象赋值给objectArray的第一个元素。

假如你使用下面的语句调用ArrayBuilder.makeArray方法:

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

在运行时,Java虚拟机(JVM)将会在下面这条语句的地方抛出一个类型转换异常(java.lang.ClassCastException):

String s = l[0].get(0);    // 此处抛出异常ClassCastException

变量l中的第一个元素具有的类型为List<Integer>,但是这条语句期望的是一个具备List<String>类型的对象。

忽略非具体化形式参数的可变参数方法的警告信息

如果你声明了一个具有参数化类型的可变参数方法。你应该确保方法的主体部分不会抛出类型转换异常ClassCastException或由于对可变参数操作不当而导致的其他类似的异常,你可以通过下列选项之一来忽略编译器产生的警告信息:

  • 在静态的非构造方法的声明上添加注解@SafeVarargs和注解@SuppressWarnings不相同的是,注解@SafeVarargs是方法约定的一部分,这个注解断言在方法的实现中会正确地处理可变参数形式的形式参数。
  • 在方法的声明上添加注解:@SuppressWarnings({"unchecked", "varargs"}) 和注解@SafeVarargs不同的是,该注解不会忽略该方法被调用时产生的警告信息。
  • 使用编译器参数命令:-Xlint:varargs

例如,在下面版本的ArrayBuilder类中有两个额外的方法addToList2和addToList3:

public class ArrayBuilder {

  public static <T> void addToList (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }

  @SuppressWarnings({"unchecked", "varargs"})
  public static <T> void addToList2 (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }

  @SafeVarargs
  public static <T> void addToList3 (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }// ...}public class HeapPollutionExample {// ...public static void main(String[] args) {// ...ArrayBuilder.addToList(listOfStringLists, stringListA, stringListB);
    ArrayBuilder.addToList2(listOfStringLists, stringListA, stringListB);
    ArrayBuilder.addToList3(listOfStringLists, stringListA, stringListB);// ...}
}

Java编译器将会为这个示例产生如下警告信息:

  • addToList:
    • 在方法定义处: [unchecked] Possible heap pollution from parameterized vararg type T
    • 当方法被调用时: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
  • addToList2:当方法被调用时 (在方法定义时不会产生警告): [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
  • addToList3: 在方法定义和方法调用时都不会产生任何警告信息。

注意: 在Java SE 5和Java SE 6中,调用非具体化形式参数的可变参数方法的程序员有责任去确定哪些情况可能导致堆污染。 然而,如果程序员没有编写过类似这样的方法,那么他/她也很难确定。在Java SE 7中,编写这些可变参数方法的程序员有责任确保他们能够正确处理可变参数形式的形式参数,并且不会导致堆污染的发生。

6 3
我们认为: 用户的主要目的,是为了获取有用的信息,而不是来点击广告的。因此本站将竭力做好内容,并将广告和内容进行分离,确保所有广告不会影响到用户的正常阅读体验。用户仅凭个人意愿和兴趣爱好点击广告。
我们坚信:只有给用户带来价值,用户才会给我们以回报。
CodePlayer技术交流群1CodePlayer技术交流群1

帮朋友打一个硬广告:

P2P网贷系统(Java版本) 新年低价大促销,多年P2P技术积累,系统功能完善(可按需定制,可支持第三方存管、银行存管),架构稳定灵活、性能优异、二次开发快速简单。 另可提供二次开发、安装部署、售后维护、安全培训等一条龙服务。

外行看热闹,内行看门道。可以自信地认为,在系统设计上,比市面上的晓风、迪蒙、方维、绿麻雀、国融信、金和盛等P2P系统要好。
深圳地区支持自带技术人员现场考察源代码、了解主要技术架构,货比三家,再决定是否购买。

也可推荐他人购买,一旦完全成交,推荐人可获得实际售价 10% 的返现。
有意向者,详情请 点击这里 联系,工作时间立即回复。

Java7新特性
泛型
可变参数
参数