Comparator.reversed()使用lambda表达式无法编译

152

我有一个包含一些用户对象的列表,我试图对该列表进行排序,但只有使用方法引用才能正常工作,使用Lambda表达式会导致编译器出错:

List<User> userList = Arrays.asList(u1, u2, u3);
userList.sort(Comparator.comparing(u -> u.getName())); // works
userList.sort(Comparator.comparing(User::getName).reversed()); // works
userList.sort(Comparator.comparing(u -> u.getName()).reversed()); // Compiler error

错误:

com\java8\collectionapi\CollectionTest.java:35: error: cannot find symbol
            userList.sort(Comparator.comparing(u -> u.getName()).reversed());
                                                     ^
symbol:   method getName()
location: variable u of type Object
1 error
4个回答

206

这是编译器类型推断机制的一个弱点。为了推断 lambda 中的 u 的类型,需要确定 lambda 的目标类型。具体做法如下:userList.sort() 需要一个类型为 Comparator<User> 的参数。在第一行中,Comparator.comparing() 需要返回类型为 Comparator<User>。这意味着 Comparator.comparing() 需要一个以 User 为参数的 Function。因此,在第一行的 lambda 中,u 必须是 User 类型,这样一切都能正常工作。

在第二和第三行中,调用 reversed() 破坏了目标类型。我不完全确定原因;因为 reversed() 的接收者和返回类型均为 Comparator<T>,所以目标类型应该被传递回接收者,但并没有传递。(就像我说的,这是一个弱点。)

在第二行中,方法引用提供了填补该空缺的额外类型信息。这种信息在第三行中缺失,所以编译器将 u 推断为 Object(最后的推断备选项),导致失败。

显然,如果可以使用方法引用,则使用方法引用即可。有时候不能使用方法引用,例如,如果您想传递额外的参数,则必须使用 lambda 表达式。在这种情况下,在 lambda 中提供一个明确的参数类型:

userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());

可能在未来的版本中,编译器可以进行改进以覆盖这种情况。


37
Lambda表达式分为_隐式类型_(参数没有显式类型)和_显式类型_,方法引用分为_精确_(没有重载)和_非精确_。当泛型方法调用在接收器位置具有lambda参数,并且类型参数无法从其他参数中完全推断出时,您需要提供显式lambda、精确方法引用、目标类型转换或显式类型见证来为泛型方法调用提供所需的附加类型信息以继续进行。 - Brian Goetz
2
@StuartMarks,您“不完全确定为什么”编译器会这样做。但是,_语言规范_说了什么?根据语言规范,是否应该有足够的信息来确定通用类型?如果是这样,那么这是一个编译器错误,应该进行记录并相应处理。否则,这是Java语言应该改进的领域。它是哪一个? - Garret Wilson
11
我认为我们可以将Brian的评论视为权威,因为他撰写了相关的规格说明书 :-) - minimalis
4
很遗憾,这些都没有解释为什么在不反转时它能够工作,而在反转时却不能工作。 - Chris311
1
我百分之百确定这是IDE的问题,因为它没有使用.reversed()时可以工作,但使用后就无法正常工作,这毫无道理。 - Carlos López Marí

113

您可以通过使用具有 Comparator.reverseOrder() 作为第二个参数的双参数 Comparator.comparing 来解决此限制:

users.sort(comparing(User::getName, reverseOrder()));

5
好的。我喜欢这种方式胜过使用显式类型的lambda表达式。或者更好的是, users.sort(reverseOrder(comparing(User::getName))); - rolve
12
请注意,上述reverseOrder(Comparator<T>)方法位于java.util.Collections中,而不是Comparator中。 - rolve
由于问题涉及到 lambda,我认为最好展示所提出的解决方案适用于像 users.sort(comparing(u -> u.getName(), reverseOrder())); 这样的 lambda,而不是方法引用(就像在 答案的第一个版本 中一样)。 - Pshemo

13

与已经被接受并获得悬赏的答案相反,这与Lambda表达式实际上没有任何关系。

以下代码可以编译:

Comparator<LocalDate> dateComparator = naturalOrder();
Comparator<LocalDate> reverseComparator = dateComparator.reversed();

而以下则不行:

Comparator<LocalDate> reverseComparator = naturalOrder().reversed();

这是因为编译器的类型推断机制不足以一次性完成两个步骤:确定reversed()方法调用需要类型参数LocalDate,因此naturalOrder()方法调用也需要相同的类型参数。

可以通过显式传递类型参数的方式调用方法。在简单情况下,这并不是必需的,因为它会被推断出来,但可以通过以下方式完成:

Comparator<LocalDate> reverseComparator = Comparator.<LocalDate>naturalOrder().reversed();

在问题中给出的示例中,这将变为:

userList.sort(Comparator.comparing<User, String>(u -> u.getName()).reversed());

但是,正如目前接受的答案所示,任何有助于编译器推断出类型User用于comparing方法调用而无需额外步骤的内容都可以使用,因此在这种情况下,您还可以明确指定lambda参数的类型或使用包含类型User的方法引用User::getName


1
请注意,Comparator.comparing 有两个类型参数。对于您的示例,假设 getName 的类型为 String,则正确的调用方式如下:userList.sort(Comparator<User, String>.comparing(u -> u.getName()).reversed()); - snappieT
@snappieT 谢谢,采纳了答案。 - herman

0
静态方法Collections.reverseOrder(Comparator<T>)似乎是提出的最优雅的解决方案。只需注意一点: Comparator.reverseOrder()要求T实现可比较并依赖于自然排序顺序。

Collections.reverseOrder(Comparator<T>)对类型T没有限制。


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