对 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
Very confused by Java 8 Comparator type inference
提问by Tranquility
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.
我一直在研究Collections.sort
和之间的区别list.sort
,特别是关于使用Comparator
静态方法以及 lambda 表达式中是否需要 param 类型。在我们开始之前,我知道我可以使用方法引用,例如Song::getTitle
来克服我的问题,但是我在这里的查询并不是我想要修复的问题,而是我想要答案的问题,即为什么 Java 编译器以这种方式处理它.
These are my finding. Suppose we have an ArrayList
of type Song
, with some songs added, there are 3 standard get methods:
这些是我的发现。假设我们有一个ArrayList
of 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 p1
anymore. So to fix this I add the type Song
to 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 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.
现在到了令人困惑的部分。对于 p laylist1.sort
,即列表,这解决了以下两个thenComparing
调用的所有编译错误。但是,对于Collections.sort
,它解决了第一个问题,而不是最后一个。我测试添加了几个额外的调用thenComparing
,它总是显示最后一个错误,除非我(Song p1)
输入参数。
Now I went on to test this further with creating a TreeSet
and 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.compare
the last call to thenComparing
shows 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 thenComparing
calls).
任何人都可以解释为什么会发生这种情况,以及为什么(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 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.
即Song
从比较方法调用的第一个 lambda 参数中删除类型,它显示了在调用比较和第一次调用thenComparing
但不是最终调用下的语法错误thenComparing
- 几乎与上面发生的情况相反!然而,所有其他3个例子,即有Objects.compare
,List.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 编译器编译时,它编译正常。
采纳答案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 p1
must 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 thenComparing
method, 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 thecomparing()
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.comparing
doesn'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 种方式之一解决此问题:
Use the new Java 8 method reference syntax
Collections.sort(playlist, Comparator.comparing(Song::getTitle) .thenComparing(Song::getDuration) .thenComparing(Song::getArtist) );
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
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()) );
使用新的 Java 8 方法参考语法
Collections.sort(playlist, Comparator.comparing(Song::getTitle) .thenComparing(Song::getDuration) .thenComparing(Song::getArtist) );
将每个比较步骤拉出到本地参考
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) );
编辑
强制比较器返回的类型(注意你需要输入类型和比较键类型)
sort( Comparator.<Song, String>comparing((s) -> s.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) );
I think the "last" thenComparing
syntax 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 List
is doing a better inferencing job than Collection
since it should do the same capture type but apparently not.
我不知道为什么List
比Collection
因为它应该做相同的捕获类型但显然没有做更好的推理工作。
回答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());