对 Java 8 Comparator 类型推断非常困惑 [英] Very confused by Java 8 Comparator type inference

查看:41
本文介绍了对 Java 8 Comparator 类型推断非常困惑的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在研究 Collections.sortlist.sort 之间的区别,特别是关于使用 Comparator 静态方法以及是否lambda 表达式中需要参数类型.在我们开始之前,我知道我可以使用方法引用,例如Song::getTitle 来克服我的问题,但我在这里的查询并不是我想要修复的问题,而是我想要答案的问题,即为什么 Java 编译器以这种方式处理它.

I've been looking at the difference between Collections.sort and list.sort, specifically regarding using the Comparator static methods and whether param types are required in the lambda expressions. Before we start, I know I could use method references, e.g. Song::getTitle to overcome my problems, but my query here is not so much something I want to fix but something I want an answer to, i.e. why is the Java compiler handling it in this way.

这些是我的发现.假设我们有一个 Song 类型的 ArrayList,添加了一些歌曲,有 3 个标准的 get 方法:

These are my finding. Suppose we have an ArrayList of type Song, with some songs added, there are 3 standard get methods:

    ArrayList<Song> playlist1 = new ArrayList<Song>();

    //add some new Song objects
    playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
    playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
    playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );

这是对两种类型的排序方法都有效的调用,没问题:

Here is a call to both types of sort method that works, no problem:

Collections.sort(playlist1, 
            Comparator.comparing(p1 -> p1.getTitle()));

playlist1.sort(
            Comparator.comparing(p1 -> p1.getTitle()));

一旦我开始链接 thenComparing,就会发生以下情况:

As soon as I start to chain thenComparing, the following happens:

Collections.sort(playlist1,
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing(p1 -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

即语法错误,因为它不再知道 p1 的类型.所以为了解决这个问题,我将 Song 类型添加到第一个参数(比较):

i.e. syntax errors because it does not know the type of p1 anymore. So to fix this I add the type Song to the first parameter (of comparing):

Collections.sort(playlist1,
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

playlist1.sort(
        Comparator.comparing((Song p1) -> p1.getTitle())
        .thenComparing(p1 -> p1.getDuration())
        .thenComparing(p1 -> p1.getArtist())
        );

现在到了令人困惑的部分.对于 playlist1.sort,即列表,这解决了以下两个 thenComparing 调用的所有编译错误.然而,对于Collections.sort,它解决了第一个,而不是最后一个.我测试向 thenComparing 添加了几个额外的调用,它总是显示最后一个错误,除非我将 (Song p1) 作为参数.

Now here comes the CONFUSING part. For playlist1.sort, i.e. the List, this solve all compilation errors, for both the following thenComparing calls. However, for Collections.sort, it solves it for the first one, but not the last one. I tested added several extra calls to thenComparing and it always shows an error for the last one, unless I put (Song p1) for the parameter.

现在我通过创建一个 TreeSet 和使用 Objects.compare 来进一步测试:

Now I went on to test this further with creating a TreeSet and with using Objects.compare:

int x = Objects.compare(t1, t2, 
                Comparator.comparing((Song p1) -> p1.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );


    Set<Song> set = new TreeSet<Song>(
            Comparator.comparing((Song p1) -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

同样的事情发生了,对于 TreeSet,没有编译错误,但是对于 Objects.compare 最后一次调用 thenComparing显示错误.

The same thing happens as in, for the TreeSet, there are no compilation errors but for Objects.compare the last call to thenComparing shows an error.

谁能解释一下为什么会发生这种情况,以及为什么在简单地调用比较方法时根本不需要使用 (Song p1)(没有进一步的 thenComparing电话).

Can anyone please explain why this is happening and also why there is no need to use (Song p1) at all when simply calling the comparing method (without further thenComparing calls).

关于同一主题的另一个查询是当我对 TreeSet 执行此操作时:

One other query on the same topic is when I do this to the TreeSet:

Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

即从比较方法调用的第一个 lambda 参数中删除类型 Song,它在比较调用和第一次调用 thenComparing 下显示语法错误,但没有显示到最终调用到 thenComparing - 几乎与上面发生的情况相反!而对于所有其他 3 个示例,即当我删除第一个 时,Objects.compareList.sortCollections.sortSong param type 它显示所有调用的语法错误.

i.e. remove the type Song from the first lambda parameter for the comparing method call, it shows syntax errors under the call to comparing and the first call to thenComparing but not to the final call to thenComparing - almost the opposite of what was happening above! Whereas, for all the other 3 examples i.e. with Objects.compare, List.sort and Collections.sort when I remove that first Song param type it shows syntax errors for all the calls.

非常感谢.

编辑以包含我在 Eclipse Kepler SR2 中收到的错误截图,我现在发现它是 Eclipse 特定的,因为在命令行上使用 JDK8 java 编译器编译时,它编译正常.

Edited to include screenshot of errors I was receiving in Eclipse Kepler SR2, which I have now since found are Eclipse specific because when compiled using the JDK8 java compiler on the command-line it compiles OK.

推荐答案

首先,您说的所有示例都可以通过参考实现(JDK 8 中的 javac)正常编译.它们在 IntelliJ 中也能正常工作,因此很有可能您看到的错误是特定于 Eclipse 的.

First, all the examples you say cause errors compile fine with the reference implementation (javac from JDK 8.) They also work fine in IntelliJ, so its quite possible the errors you're seeing are Eclipse-specific.

您的潜在问题似乎是:为什么当我开始链接时它会停止工作."原因是,虽然 lambda 表达式和泛型方法调用在作为方法参数出现时是 poly 表达式(它们的类型是上下文敏感的),但当它们作为方法接收器表达式出现时,它们不是.

Your underlying question seems to be: "why does it stop working when I start chaining." The reason is, while lambda expressions and generic method invocations are poly expressions (their type is context-sensitive) when they appear as method parameters, when they appear instead as method receiver expressions, they are not.

当你说

Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));

有足够的类型信息来解决comparing() 的类型参数和参数类型p1.comparing() 调用从 Collections.sort 的签名中获取其目标类型,因此已知 comparing() 必须返回一个 Comparator,因此 p1 必须是 Song.

there is enough type information to solve for both the type argument of comparing() and the argument type p1. The comparing() call gets its target type from the signature of Collections.sort, so it is known comparing() must return a Comparator<Song>, and therefore p1 must be Song.

但是当你开始链接时:

Collections.sort(playlist1,
                 comparing(p1 -> p1.getTitle())
                     .thenComparing(p1 -> p1.getDuration())
                     .thenComparing(p1 -> p1.getArtist()));

现在我们遇到了问题.我们知道复合表达式 comparing(...).thenComparing(...) 的目标类型是 Comparator,但是因为链,comparing(p -> p.getTitle()),是一个泛型方法调用,我们不能从它的其他参数推断它的类型参数,我们有点不走运.由于我们不知道这个表达式的类型,我们也不知道它有一个thenComparing方法等等.

now we've got a problem. We know that the compound expression comparing(...).thenComparing(...) has a target type of Comparator<Song>, but because the receiver expression for the chain, comparing(p -> p.getTitle()), is a generic method call, and we can't infer its type parameters from its other arguments, we're kind of out of luck. Since we don't know the type of this expression, we don't know that it has a thenComparing method, etc.

有几种方法可以解决这个问题,所有方法都涉及注入更多类型信息,以便链中的初始对象可以正确键入.在这里,按受欢迎程度降低和侵入性增加的粗略顺序排列:

There are several ways to fix this, all of which involve injecting more type information so that the initial object in the chain can be properly typed. Here they are, in rough order of decreasing desirability and increasing intrusiveness:

  • 使用精确的方法引用(没有重载的方法),例如 Song::getTitle.然后,这提供了足够的类型信息来推断 comparing() 调用的类型变量,因此给它一个类型,从而继续沿链向下.
  • 使用显式 lambda(就像您在示例中所做的那样).
  • comparing() 调用提供类型见证:Comparator.comparing(...).
  • 通过将接收器表达式转换为 Comparator,提供带有转换的显式目标类型.
  • Use an exact method reference (one with no overloads), like Song::getTitle. This then gives enough type information to infer the type variables for the comparing() call, and therefore give it a type, and therefore continue down the chain.
  • Use an explicit lambda (as you did in your example).
  • Provide a type witness for the comparing() call: Comparator.<Song, String>comparing(...).
  • Provide an explicit target type with a cast, by casting the receiver expression to Comparator<Song>.

这篇关于对 Java 8 Comparator 类型推断非常困惑的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆