Java 8比较器types推断非常困惑
我一直在查看Collections.sort
和list.sort
之间的区别,特别是关于使用Comparator
静态方法以及在lambdaexpression式中是否需要参数types。 在我们开始之前,我知道我可以使用方法引用,例如Song::getTitle
来克服我的问题,但是我的查询并不是我想要解决的问题,而是我想要的答案,即为什么Java编译器要处理就这样。
这些是我的发现。 假设我们有一个Song
types的ArrayList
,添加了一些歌曲,有3个标准的获取方法:
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") );
这是对这两种types的sorting方法的调用,没有问题:
Collections.sort(playlist1, Comparator.comparing(p1 -> p1.getTitle())); playlist1.sort( Comparator.comparing(p1 -> p1.getTitle()));
一旦我开始链接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()) );
即语法错误,因为它不知道p1
的types了。 所以要解决这个问题,我把typesSong
添加到比较的第一个参数:
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()) );
现在来了CONFUSING部分。 对于p laylist1.sort
,即列表,这将解决所有的编译错误,对于后面的thenComparing
调用。 然而,对于Collections.sort
,它解决了它的第一个,但不是最后一个。 我testing添加了几个额外的调用thenComparing
,它总是显示最后一个错误,除非我把(Song p1)
的参数。
现在我继续进行testing,创build一个TreeSet
并使用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
显示一个错误。
任何人都可以解释为什么会发生这种情况,以及为什么在简单地调用比较方法(没有进一步thenComparing
呼叫)时根本不需要使用(Song p1)
)。
对同一个主题的另一个查询是当我这样做TreeSet
:
Set<Song> set = new TreeSet<Song>( Comparator.comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) );
即从比较方法调用的第一个lambda参数中删除typesSong
,它会在比较调用和第一次调用thenComparing
时显示语法错误,而不是最终调用thenComparing
– 几乎与上面发生的情况相反! 而对于所有其他3个例子,即当我删除第一个Song
参数types时,它与Objects.compare
, List.sort
和Collections.sort
,它显示所有调用的语法错误。
提前谢谢了。
编辑包括我在Eclipse Kepler SR2中收到的错误的截图,现在我发现它们是Eclipse特有的,因为在命令行上使用JDK8 java编译器进行编译时,它会编译OK。
首先,你说的所有例子都会导致错误使用参考实现(JDK 8中的javac)编译。它们在IntelliJ中也可以很好地工作,所以很可能你看到的错误是Eclipse特有的。
你的根本问题似乎是:“当我开始链接时,为什么它停止工作。” 原因是,当lambdaexpression式和generics方法调用是polyexpression式 (它们的types是上下文敏感的),当它们作为方法参数出现时,当它们作为方法接收expression式出现时,它们不是。
当你说
Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
comparing()
和参数typesp1
的types参数都有足够的types信息来解决。 comparing()
调用从Collections.sort
的签名中获取目标types,所以comparing()
必须返回一个Comparator<Song>
,因此p1
必须是Song
。
但是当你开始链接:
Collections.sort(playlist1, comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()));
现在我们遇到了问题。 我们知道comparing(...).thenComparing(...)
的复合expression式具有Comparator<Song>
的目标types,但是因为链接收expression式, comparing(p -> p.getTitle())
,是一个通用的方法调用,我们不能从其他的参数中推断出它的types参数,我们倒霉了。 既然我们不知道这个expression式的types,我们不知道它有一个thenComparing
方法等等。
有几种方法可以解决这个问题,所有这些都需要注入更多的types信息,以便链中的初始对象可以正确input。 在这里,他们正在逐渐降低可取性和不断增加的侵入性:
- 使用一个精确的方法引用(一个没有重载),如
Song::getTitle
。 这然后给出足够的types信息来推断comparing()
调用的typesvariables,因此给它一个types,并因此继续下去。 - 使用明确的lambda(如你在你的例子中所做的那样)。
- 为
comparing()
调用提供types见证:Comparator.<Song, String>comparing(...)
。 - 通过将接收器expression式转换为
Comparator<Song>
来提供带有强制转换的显式目标types。
问题是types推理。 如果不在第一次比较中添加(Song s)
, comparator.comparing
不知道input的types,因此默认为Object。
你可以解决这个问题的三种方法之一:
-
使用新的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) );
编辑
-
强制比较器返回的types(注意,您需要inputtypes和比较键types)
sort( Comparator.<Song, String>comparing((s) -> s.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) );
我认为“last” thenComparing
语法错误是误导你的。 这实际上是整个链的types问题,只是编译器只将链的末尾标记为语法错误,因为那是最后的返回types不匹配我猜。
我不知道为什么List
比Collection
更好的推理工作,因为它应该做相同的捕获types,但显然不是。
playlist1.sort(...)
从playlist1的声明中为typesvariablesE创build一个歌曲边界,该声明将“涟漪化”到比较器。
在Collections.sort(...)
,没有这样的限制,从第一个比较器的types推断是不足以让编译器推断其余的。
我想你会从Collections.<Song>sort(...)
得到“正确”的行为,但是没有安装java 8来为你testing。
处理这个编译时错误的另一种方法是:
强制转换你的第一个比较函数的variables,然后很好走。 我已经sorting了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());