最大元素 - 孙的答案VS我的

时间:2013-11-29 00:29:56

标签: java generics

在阅读了Sun关于泛型的文档后,我转到了http://docs.oracle.com/javase/tutorial/java/generics/QandE/generics-questions.html的Q和E部分。

对于Q8 -

  

编写一个通用方法来查找列表[begin,end]范围内的最大元素。

我写的代码是:

private static <T extends Comparable<T>> T max(List<T> l, int i, int j) {
    List<T> sublist = l.subList(i, j);
    System.out.println("Sublist "+sublist);
    int c = 0;T max = null;
    for(T elem: sublist) {
        if(c == 0 || max.compareTo(elem) < 0) {
            max = elem;
        } ++c;
    }
    return max;
}

和Sun的回答是:

public static <T extends Object & Comparable<? super T>>

    T max(List<? extends T> list, int begin, int end) {

    T maxElem = list.get(begin);

    for (++begin; begin < end; ++begin)
        if (maxElem.compareTo(list.get(begin)) < 0)
            maxElem = list.get(begin);
    return maxElem;
}

有人可以告诉我一个例子,Sun的版本比我的更好吗?

  编辑:我想比较两种解决方案的效率,主要是基于方法声明中使用的类型参数/边界而不是逻辑,例如在什么情况下Sun的版本更适合函数的调用者?基本上我不明白你为什么需要Sun使用的<T extends Object & Comparable<? super T>List<? extends T>而不是我使用过的。

我非常感激一个例子,因为我已被理论所压倒。 (对不起,如果这听起来很粗鲁,但我不是故意的。)

提前致谢, 穆斯塔法

1 个答案:

答案 0 :(得分:7)

这里有两个独立的仿制药问题。

为什么要使用交叉类型?

也就是说,为什么解决方案使用T extends Object & Comparable<T>而不是更简单的T extends Comparable<T>? (我在本节中省略了通配符;我将在下面介绍它们。)

我不认为Object & Comparable<T>与类型系统中的Comparable<T>有任何不同,因为每个对象都延伸Object。也就是说,没有类型T扩展Comparable<T>,但不会扩展Object & Comparable<T>

但是有一点不同。交叉点类型擦除到交集的第一个组件,因此T extends Object & Comparable<T>将删除到Object,而T extends Comparable<T>将删除到Comparable。考虑max方法的两个替代声明:

<T extends Comparable<T>> T max1(List<T> list) { ... }

<T extends Object & Comparable<T>> T max2(List<T> list) { ... }

如果使用javap -s转储这些方法的签名,您可以看到显示已删除类型的内部签名:

<T extends java/lang/Comparable<T>> T max1(java.util.List<T>);
  Signature: (Ljava/util/List;)Ljava/lang/Comparable;

<T extends java/lang/Object & java/lang/Comparable<T>> T max2(java.util.List<T>);
  Signature: (Ljava/util/List;)Ljava/lang/Object;

谁在乎擦除类型? JVM呢。 JVM基于匹配参数类型和返回类型来查找方法。因此,擦除的返回类型可能很重要。

事实上,从二进制兼容性的角度来看,它具有重要意义。在Java SE 5之前,当引入泛型时,声明了Collections.max方法并且删除了签名,如下所示:

public static Object max(Collection coll)
  Signature: (Ljava/util/Collection;)Ljava/lang/Object;

在Java SE 5及更高版本中,声明和删除的签名是:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
  Signature: (Ljava/util/Collection;)Ljava/lang/Object;

至关重要的是,删除的签名相同

如果没有使用交集类型声明Java SE 5声明,它将如下所示:

public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)
  Signature: (Ljava/util/Collection;)Ljava/lang/Comparable;

这将是二进制不兼容。针对旧版JDK编译的二进制文件将引用返回max的{​​{1}}版本。如果使用此备用,不兼容的声明针对JDK运行,则Object的唯一版本将返回max,导致在链接时抛出Comparable

因此,使用NoSuchMethodError实际上是为了控制此声明的擦除,这是由二进制兼容性考虑因素驱动的。在这一点上,该教程似乎有些误导。 Java SE中<T extends Object & Comparable<T>>的实际声明是这种二进制兼容性的方式。但如果你是第一次宣布这种方法,我不认为以这种方式使用交集类型是有用的。

为什么声明中使用了通配符?

即代替:

Collections.max

为什么使用通配符:

static <T extends Comparable<T>> T max(List<T> list)

这里,通配符对于在存在子类型时更灵活地使用该方法是必要的。请考虑以下事项:

static <T extends Comparable<? super T>> T max(List<? extends T> list)

如果使用了非通配符声明,则不会匹配class A implements Comparable<A> { ... } class B extends A { } List<B> bList = ...; B bMax = max(bList); 。为了完成这项工作,T是必要的。这样可以将Comparable<? super T>推断为T,一切正常。

我必须承认,我无法找到一个示例,说明在这种情况下需要B的原因。如果参数仅被声明为List<? extends T>,则此示例可以正常工作。可能是List<T>用于文档目的,以指示仅从列表中检索元素,并且不修改列表。 (有关这方面的更多信息,请参阅泛型章节中的Bloch的 Effective Java ,他将讨论PECS - “Producer Extends,Consumer Super”;或Naftalin和Wadler的 Java Generics and Collections 他们讨论“放弃和获取原则”。)


链接:

Why is T bound by Object in the Collections.max() signature?

Angelika Langer's FAQ entry

Java Generics: What is PECS?

Why do we need bounded wilcard in Collections.max() method