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

Mus*_*afa 5 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;
}
Run Code Online (Sandbox Code Playgroud)

而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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

一个例子非常受欢迎,因为我已经被理论所压倒.(对不起,如果听起来很粗鲁,但我不是故意的).

提前谢谢,穆斯塔法

Stu*_*rks 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) { ... }
Run Code Online (Sandbox Code Playgroud)

如果使用转储这些方法的签名,则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;
Run Code Online (Sandbox Code Playgroud)

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

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

public static Object max(Collection coll)
  Signature: (Ljava/util/Collection;)Ljava/lang/Object;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

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

也就是说,而不是:

static <T extends Comparable<T>> T max(List<T> list)
Run Code Online (Sandbox Code Playgroud)

为什么使用通配符:

static <T extends Comparable<? super T>> T max(List<? extends T> list)
Run Code Online (Sandbox Code Playgroud)

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

class A implements Comparable<A> { ... }

class B extends A { }

List<B> bList = ...;
B bMax = max(bList);
Run Code Online (Sandbox Code Playgroud)

如果使用了非通配符声明,则不会T匹配.为了使这项工作,Comparable<? super T>是必要的.这允许T推断为B一切正常.

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


链接:

为什么T在Collections.max()签名中被Object绑定?

Angelika Langer的FAQ条目

Java Generics:什么是PECS?

为什么我们需要在Collections.max()方法中使用有界wilcard