【问题标题】:Comparator.reversed() does not compile using lambdaComparator.reversed() 不使用 lambda 编译
【发布时间】:2014-09-30 02:37:22
【问题描述】:

我有一个包含一些用户对象的列表,我正在尝试对列表进行排序,但只能使用方法引用,使用 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


    【解决方案1】:

    这是编译器类型推断机制的一个弱点。为了推断 lambda 中 u 的类型,需要建立 lambda 的目标类型。这是按如下方式完成的。 userList.sort() 期待 Comparator&lt;User&gt; 类型的参数。在第一行,Comparator.comparing() 需要返回Comparator&lt;User&gt;。这意味着Comparator.comparing() 需要一个带有User 参数的Function。因此,在第一行的 lambda 中,u 必须是 User 类型,并且一切正常。

    在第二行和第三行中,目标输入因对reversed() 的调用而中断。我不完全确定为什么; reversed() 的接收器和返回类型都是Comparator&lt;T&gt;,所以看起来目标类型应该传播回接收器,但事实并非如此。 (就像我说的,这是一个弱点。)

    在第二行中,方法引用提供了填补这一空白的附加类型信息。第三行中没有此信息,因此编译器将u 推断为Object(最后的推断回退),但失败了。

    显然,如果您可以使用方法引用,那么就这样做吧。有时你不能使用方法引用,例如,如果你想传递一个额外的参数,那么你必须使用 lambda 表达式。在这种情况下,您将在 lambda 中提供显式参数类型:

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

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

    【讨论】:

    • Lambda 分为 implicitly-typed(参数没有清单类型)和 explicitly-typed;方法引用分为exact(无重载)和inexact。当接收者位置的泛型方法调用具有 lambda 参数,并且无法从其他参数完全推断类型参数时,您需要提供显式 lambda、精确方法引用、目标类型转换或显式类型见证通用方法调用,以提供继续所需的其他类型信息。
    • @StuartMarks,你“不完全确定为什么”编译器会这样。但是语言规范是怎么说的呢?根据语言规范,是否应该有足够的信息来确定泛型类型?如果是这样,这是一个编译器错误,应该提交并相应地处理。否则,这是 Java 语言应该改进的领域。是哪个?
    • 我认为我们可以将 Brian 的 cmets 视为权威,因为他编写了有问题的规范 :-)
    • 遗憾的是,这些都不能解释为什么它在没有反转的情况下工作,而在反转的情况下不工作。
    • 我 100% 确定这是 IDE 的问题,因为没有 .reversed() 但没有它,它没有意义。
    【解决方案2】:

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

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

    【讨论】:

    • 不错。我比使用显式类型的 lambda 更喜欢这个。或者,更好的是,users.sort(reverseOrder(comparing(User::getName)));
    • 请注意,上面的reverseOrder(Comparator&lt;T&gt;)方法是在java.util.Collections中,而不是在Comparator中。
    • 由于问题是关于 lambda IMO 的问题,因此最好表明提议的解决方案适用于像 users.sort(comparing(u -&gt; u.getName(), reverseOrder())); 这样的 lambda,而不是方法引用(就像在 @987654321 中一样@)。
    【解决方案3】:

    与已获得赏金的已接受和赞成的答案相反,这与 lambdas 没有任何关系。

    以下编译:

    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());
    

    但如当前接受的答案所示,任何有助于编译器推断类型 Usercomparing 方法调用而无需采取额外步骤的方法都将起作用,因此在这种情况下,您还可以指定 lambda 参数的类型显式地或使用方法引用User::getName,其中还包括User 类型。

    【讨论】:

    • 注意Comparator.comparing有两个类型参数。对于您的示例,假设 getName 的类型为 String,正确的调用将是:userList.sort(Comparator&lt;User, String&gt;.comparing(u -&gt; u.getName()).reversed());
    • @snappieT 谢谢,修改了答案。
    【解决方案4】:

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

    Collections.reverseOrder(Comparator&lt;T&gt;)T 类型没有应用限制

    【讨论】:

      猜你喜欢
      • 2021-06-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-24
      • 2021-06-18
      相关资源
      最近更新 更多