Java 8比较器types推断非常困惑

我一直在查看Collections.sortlist.sort之间的区别,特别是关于使用Comparator静态方法以及在lambdaexpression式中是否需要参数types。 在我们开始之前,我知道我可以使用方法引用,例如Song::getTitle来克服我的问题,但是我的查询并不是我想要解决的问题,而是我想要的答案,即为什么Java编译器要处理就这样。

这些是我的发现。 假设我们有一个Songtypes的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.compareList.sortCollections.sort ,它显示所有调用的语法错误。

提前谢谢了。

编辑包括我在Eclipse Kepler SR2中收到的错误的截图,现在我发现它们是Eclipse特有的,因为在命令行上使用JDK8 java编译器进行编译时,它会编译OK。

在Eclipse中对错误进行排序

首先,你说的所有例子都会导致错误使用参考实现(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。

你可以解决这个问题的三种方法之一:

  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. 强制比较器返回的types(注意,您需要inputtypes和比较键types)

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

我认为“last” thenComparing语法错误是误导你的。 这实际上是整个链的types问题,只是编译器只将链的末尾标记为语法错误,因为那是最后的返回types不匹配我猜。

我不知道为什么ListCollection更好的推理工作,因为它应该做相同的捕获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());