为什么openjdk使用不安全的构造?

时间:2013-10-24 06:07:36

标签: java generics openjdk raw-types

阅读openjdk7的一些代码,我发现方法Collections.reverse实现为(我删除了RandomAccess列表的一些优化):

public static void reverse(List<?> list) {
    int size = list.size();
    ListIterator fwd = list.listIterator();
    ListIterator rev = list.listIterator(size);
    for (int i = 0, mid = list.size() >> 1; i < mid; i++) {
        Object tmp = fwd.next();
        fwd.set(rev.previous());
        rev.set(tmp);
    }
}

ListIterators的两个初始化都生成未经检查的警告(并且代码中没有@SupressWarnings anotation)。

对我而言,实施的保存和简单方法是:

public static <E> void reverse2(List<E> list) {
    int size = list.size();
    ListIterator<E> fwd = list.listIterator();
    ListIterator<E> rev = list.listIterator(size);
    for (int i = 0, mid = list.size() >> 1; i < mid; i++) {
        E tmp = fwd.next();
        fwd.set(rev.previous());
        rev.set(tmp);
    }
}

这是完全安全的。

我的问题是:

  • 为什么openjdk使用原始类型的不安全代码?
  • 为什么警告没有被抑制?

3 个答案:

答案 0 :(得分:0)

这是一种权衡。 JDK开发人员接受了警告,以减少其他开发人员的警告。如果您使用List编译旧代码,即作为原始类型,则可以将其传递给Collections.reverse(),而不会发出警告,因为它是安全的。但是,如果Collections.reverse()public static <E> void reverse(List<E> list)中的类型参数,则在尝试使用原始类型List调用时会收到警告。

答案 1 :(得分:0)

答案 2 :(得分:0)

从概念上讲,reverse()只需要List个参数。它根本不关心,也不需要将参数与其他类型相关联。因此,void reverse(List<?> list)是最简单的(因此也是最好的)签名。

现在,要在泛型中正确地编写它,在reverse的实现中,需要使用列表的类型参数,因为我们需要从列表中取一个元素,然后将其放回去,我们需要一种类型来表达这个临时值。

在Java中执行此操作的唯一方法是为整个方法声明一个泛型参数 - 使其成为通用方法 - 正如reverse2所做的那样:<E> void reverse2(List<E> list)

但是,这会更改签名(对外部可见)。这里的E仅用于参数中的一个位置,从类型的角度来看基本上是不必要的。我们需要在内部使用E的事实是外部代码不需要关心的实现细节。

有一种方法可以使用完全安全的构造并保留签名void reverse(List<?> list):使用捕获帮助程序。即创建一个带List<?>的辅助方法,它所做的只是调用泛型方法(然后可以将其设为私有):

public static void reverse(List<?> list) {
    reverse2(list);
}

(如果你不理解为什么在泛型下这是正确的,提示:它是由于捕获。)

尽管这似乎具有两全其美(更简单的签名+安全构造),但缺点是从运行时的角度来看,它是浪费。我们有一个方法采用一个参数,它所做的只是将它直接传递给另一个参数获取一个参数。这看起来完全无关紧要。在类型擦除之后 - 您将有一种方法将List调用另一种方法List。 (如果我向你展示了这样的功能,并且你不了解泛型,你可能会立即认为这是不必要的。)

因此,为了提高效率,他们决定在内部代码中牺牲完全安全的构造,以保持简单的签名和单个函数调用的效率,而不是两个。他们使用的构造无论如何都在内部代码中,所以除了Java库开发人员之外没有人应该关心它;在类型擦除之后,代码与安全代码完全相同,因此二进制文件没有区别。而编译代码的签名和效率都会影响库的用户。