Comparator.reversed() 无法使用 lambda 进行编译

问题描述 投票:0回答:5

我有一个包含一些 User 对象的列表,我正在尝试对列表进行排序,但只能使用方法引用,使用 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
java lambda java-8 comparator method-reference
5个回答
214
投票

这是编译器类型推断机制的一个弱点。为了推断 lambda 中

u
的类型,需要建立 lambda 的目标类型。这是按如下方式完成的。
userList.sort()
需要一个
Comparator<User>
类型的参数。第一行中,
Comparator.comparing()
需要返回
Comparator<User>
。这意味着
Comparator.comparing()
需要一个带有
Function
参数的
User
。因此,在第一行的 lambda 中,
u
必须是
User
类型,并且一切正常。

在第二行和第三行中,目标键入因对

reversed()
的调用而中断。我不太清楚为什么;
reversed()
的接收者和返回类型都是
Comparator<T>
,因此目标类型似乎应该传播回接收者,但事实并非如此。 (就像我说的,这是一个弱点。)

在第二行中,方法引用提供了填补此空白的附加类型信息。第三行缺少此信息,因此编译器将

u
推断为
Object
(最后手段的推理回退),但失败了。

显然,如果您可以使用方法引用,那么就这样做,它就会起作用。有时您无法使用方法引用,例如,如果您想传递附加参数,则必须使用 lambda 表达式。在这种情况下,您需要在 lambda 中提供显式参数类型:

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

编译器可能会得到增强,以在未来版本中涵盖这种情况。


118
投票

您可以通过使用双参数

Comparator.comparing
以及
Comparator.reverseOrder()
作为第二个参数来解决此限制:

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

18
投票

与已获得赏金的已接受和赞成的答案相反,这实际上与 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::getName
,其中还包含类型
User


0
投票

静态方法

Collections.reverseOrder(Comparator<T>)
似乎是已提出的最优雅的解决方案。只是一个警告:
Comparator.reverseOrder()
要求T实现可比较并依赖于自然排序顺序。

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


0
投票

其实编译出来的coe如下:

List<Employee> list = ss.stream()
.sorted(Comparator.comparing(Employee::getName).thenComparing(e -> e.getSurname()).thenComparing(e -> e.getSalary()))
.toList();

这里,第一次调用是方法引用

Employee::getName
。但是,如果您将方法引用替换为 lambda 表达式(如
e -> e.getName()
),则代码将不再编译。

我仍然认为这是Java编译器的一个bug

© www.soinside.com 2019 - 2024. All rights reserved.