Collections.sort()声明:为什么要使用<? super T>而不是<T>?

24
为什么 Collections.sort(List<T>) 的签名是这样的:
public static <T extends Comparable<? super T>> void sort(List<T> list) 

而不是:
public static <T extends Comparable<T>> void sort(List<? extends T> list)
  • 我理解它们都会起到同样的作用,那么为什么框架开发者使用了第一种选项?
  • 还是说这些声明真的有所不同?
3个回答

19
你提议的签名在Java-8中可能有效。但是在之前的Java版本中,类型推断并不那么智能。考虑一下你有一个List。请注意,java.sql.Date扩展了java.util.Date,它实现了Comparable。编译时需要注意。
List<java.sql.Date> list = new ArrayList<>();
Collections.sort(list);

它在Java-7中完美运行。这里T被推断为java.sql.Date,实际上是Comparable<java.util.Date>,也是Comparable<? super java.sql.Date>。然而,让我们尝试使用您的签名:

public static <T extends Comparable<T>> void sort(List<? extends T> list) {}

List<java.sql.Date> list = new ArrayList<>();
sort(list);

在这里,T 应被推断为 java.util.Date。然而 Java 7 规范不允许这样的推断。因此,这段代码可以在 Java 8 中编译,但在 Java 7 下编译时会失败:

Main.java:14: error: method sort in class Main cannot be applied to given types;
        sort(list);
        ^
  required: List<? extends T>
  found: List<Date>
  reason: inferred type does not conform to declared bound(s)
    inferred: Date
    bound(s): Comparable<Date>
  where T is a type-variable:
    T extends Comparable<T> declared in method <T>sort(List<? extends T>)
1 error

Java-8中大大改进了类型推断。现在专门有一个独立的JLS 第18章 来讲解它,而在Java-7中规则更简单

1
但是,static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> c) 这个代码为什么能正常工作呢?这又是一个推断问题吗? - prvn
@prvn,你提出的签名中使用了Comparable<T>,但实际上应该使用Comparable<? super T><T extends Comparable<? super T>> void sort(List<? extends T> list)也可以工作,但在这里添加? extends T是完全不必要的,而在max方法中是合理的(因为max返回值)。 - Tagir Valeev
8
还有一个要点。当你有一个像List<? extends T> list这样的通配符类型时,你不能在方法内部执行list.set(index, list.get(anotherIndex)),因为通配符类型的工作方式。这可以通过使用内部帮助方法来捕获? extends T到另一个类型变量(或者像内部实现经常做的那样使用未检查的操作)来规避,但是对于将要修改的列表而言,没有通配符的类型更加简洁。 - Holger
@Holger:但这是实现者的内部实现问题。我们只关注公共API,不应受到内部实现细节的影响。 - newacct
2
@newacct:如果两个变量对调用者没有区别,你很可能会选择允许更容易实现的那个。然而,开发人员习惯于最初认为List<? extends ...>类型的参数不会因为PECS规则而被修改,如果你将其用于sort方法,就需要再次查看该方法。这不仅仅是一个实现问题。 - Holger

9
// 0
public static <T extends Comparable<? super T>> void sort0(List<T> list) 

// 1
public static <T extends Comparable<T>> void sort1(List<? extends T> list)

这些签名不同是因为它们对类型TComparable中的类型参数之间的关系在T的定义中施加了不同的要求。
例如,假设你有这个类:
class A implements Comparable<Object> { ... }

那么,如果您有以下需求:
List<A> list = ... ;
sort0(list); // works
sort1(list); // fails
sort1 失败的原因是没有类型 T,它既能与自身进行比较,又是列表类型的子类型或超类型。
事实证明类 A 是畸形的,因为实现了 Comparable 接口的对象需要满足某些要求。特别是,反转比较应该反转结果的符号。我们可以将一个 A 的实例与一个 Object 进行比较,但不能反过来,因此违反了这个要求。但请注意,这是 Comparable 语义上的要求,并不是由类型系统强制的。仅考虑类型系统,这两个 sort 声明确实有所不同。

4
它们的不同之处在于? super TT更加灵活。这是一个下限通配符(部分链接的Java教程说)。引用如下:

List<Integer>这个术语比List<? super Integer>更加具有限制性,因为前者只匹配类型为Integer的列表,而后者匹配任何类型的列表,该类型是Integer的祖先。

Integer替换为T,这意味着Tjava.lang.Object

但我正在增加sort()函数参数的灵活性..使用? extends。 - prvn
1
是的,没有人说 List<? super Integer>List<Integer> 是相同的。第二个版本使用了 extends,问题是这是否使它相等。 - JojOatXGME
1
我认为你的回答并没有直接回答问题。 - yuxh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接