对 Java 8 Comparator 类型推断非常困惑

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24436871/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 12:12:10  来源:igfitidea点击:

Very confused by Java 8 Comparator type inference

javasortinglambdajava-8comparator

提问by Tranquility

I've been looking at the difference between Collections.sortand list.sort, specifically regarding using the Comparatorstatic methods and whether param types are required in the lambda expressions. Before we start, I know I could use method references, e.g. Song::getTitleto 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.

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

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

这些是我的发现。假设我们有一个ArrayListof type Song,添加了一些歌曲,有 3 个标准的 get 方法:

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

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

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

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

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

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

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

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

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

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

现在我继续通过创建 aTreeSet和 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())
            );

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

发生与 相同的事情,对于TreeSet,没有编译错误,但Objects.compare最后一次调用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 thenComparingcalls).

任何人都可以解释为什么会发生这种情况,以及为什么(Song p1)在简单地调用比较方法(无需进一步thenComparing调用)时根本不需要使用。

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

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

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

i.e. remove the type Songfrom the first lambda parameter for the comparing method call, it shows syntax errors under the call to comparing and the first call to thenComparingbut 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.sortand Collections.sortwhen I remove that first Songparam type it shows syntax errors for all the calls.

Song从比较方法调用的第一个 lambda 参数中删除类型,它显示了在调用比较和第一次调用thenComparing但不是最终调用下的语法错误thenComparing- 几乎与上面发生的情况相反!然而,所有其他3个例子,即有Objects.compareList.sort而且Collections.sort当我删除第一个Song参数类型它显示语法错误的所有电话。

Many thanks in advance.

提前谢谢了。

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.

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

Sort errors in Eclipse

Eclipse 中的排序错误

采纳答案by Brian Goetz

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.

首先,您所说的所有导致错误的示例都可以使用参考实现(来自 JDK 8 的 javac)编译良好。它们在 IntelliJ 中也可以正常工作,因此您看到的错误很可能是特定于 Eclipse 的错误。

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.

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

When you say

当你说

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

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 p1must be Song.

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

But when you start chaining:

但是当你开始链接时:

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

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 thenComparingmethod, etc.

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

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:

有几种方法可以解决这个问题,所有这些方法都涉及注入更多类型信息,以便可以正确键入链中的初始对象。它们是,按照降低可取性和增加侵入性的粗略顺序:

  • 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>.
  • 使用精确的方法引用(没有重载的方法),例如Song::getTitle. 然后这提供了足够的类型信息来推断comparing()调用的类型变量,因此给它一个类型,因此继续沿着链向下。
  • 使用显式 lambda(如您在示例中所做的那样)。
  • comparing()调用提供类型见证:Comparator.<Song, String>comparing(...).
  • 通过将接收器表达式强制转换为 ,提供带有强制转换的显式目标类型Comparator<Song>

回答by dkatzel

The problem is type inferencing. Without adding a (Song s)to the first comparison, comparator.comparingdoesn't know the type of the input so it defaults to Object.

问题是类型推断。如果没有(Song s)在第一次比较中添加 a ,comparator.comparing则不知道输入的类型,因此默认为 Object。

You can fix this problem 1 of 3 ways:

您可以通过以下 3 种方式之一解决此问题:

  1. Use the new Java 8 method reference syntax

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. Pull out each comparison step into a local reference

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    EDIT

  3. Forcing the type returned by the Comparator (note you need both the input type and the comparison key type)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    
  1. 使用新的 Java 8 方法参考语法

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. 将每个比较步骤拉出到本地参考

      Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist());
    
      Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration());
    
        Collections.sort(playlist,
                byName
                .thenComparing(byDuration)
                );
    

    编辑

  3. 强制比较器返回的类型(注意你需要输入类型和比较键类型)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

I think the "last" thenComparingsyntax error is misleading you. It's actually a type problem with the whole chain, it's just the compiler only marking the end of the chain as a syntax error because that's when the final return type doesn't match I guess.

我认为“最后一个”thenComparing语法错误误导了您。这实际上是整个链的类型问题,只是编译器仅将链的末尾标记为语法错误,因为我猜这是最终返回类型不匹配的时候。

I'm not sure why Listis doing a better inferencing job than Collectionsince it should do the same capture type but apparently not.

我不知道为什么ListCollection因为它应该做相同的捕获类型但显然没有做更好的推理工作。

回答by amalloy

playlist1.sort(...)creates a bound of Song for the type variable E, from the declaration of playlist1, which "ripples" to the comparator.

playlist1.sort(...)为类型变量 E 创建一个 Song 的边界,从 playlist1 的声明开始,它“涟漪”到比较器。

In Collections.sort(...), there is no such bound, and the inference from the type of the first comparator is not enough for the compiler to infer the rest.

在 中Collections.sort(...),没有这样的界限,从第一个比较器的类型推断出的内容不足以让编译器推断出其余部分。

I think you would get "correct" behavior from Collections.<Song>sort(...), but don't have a java 8 install to test it out for you.

我认为您会从 中获得“正确”的行为Collections.<Song>sort(...),但没有安装 java 8 来为您测试。

回答by Rajni Gangwar

Another way to deal with this compile time error:

处理此编译时错误的另一种方法:

Cast your first comparing function's variable explicitly and then good to go. I have sort the list of org.bson.Documents object. Please look at sample code

显式转换您的第一个比较函数的变量,然后就可以了。我已经对 org.bson.Documents 对象的列表进行了排序。请看示例代码

Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder())
                       .thenComparing(hist -> (Date) hist.get("promisedShipDate"))
                       .thenComparing(hist -> (Date) hist.get("lastShipDate"));
list = list.stream().sorted(comparator).collect(Collectors.toList());