我一直在研究Collections.sort
和list.sort
之间的区别,特别是关于使用Comparator
静态方法和在lambda表达式中是否需要参数类型。在开始之前,我知道我可以使用方法引用,例如Song::getTitle
来克服我的问题,但我在这里的查询与其说是我想修复的东西,不如说是我想得到答案的东西。也就是为什么Java编译器以这种方式处理它。
以下是我的发现:假设我们有一个Song
类型的ArrayList
,并添加了一些歌曲,则有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") );
下面是对这两种排序方法的调用,都能正常工作:
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
的类型。因此,为了解决这个问题,我添加类型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())
);
现在是CONFUSING部分。对于p laylist1.sort
,即List,这解决了所有编译错误,对于以下两个thenComparing
调用。然而,对于Collections.sort
,它解决了第一个,但不是最后一个。我测试添加了几个额外的调用thenComparing
,它总是显示最后一个错误,除非我把(Song p1)
作为参数。
现在,我继续创建一个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参数中删除类型Song
,它在对比较的调用和对thenComparing
的第一个调用下显示语法错误,但在对thenComparing
的最后一个调用下没有显示语法错误-几乎与上面发生的情况相反!然而,对于所有其他3个示例,即,具有Objects.compare
的示例,List.sort
和Collections.sort
当我删除第一个Song
参数类型时,所有调用都显示语法错误。
编辑以包括我在Eclipse Kepler SR 2中收到的错误的屏幕截图,我现在发现这些错误是Eclipse特有的,因为当在命令行上使用JDK 8 java编译器编译时,它编译正常。
4条答案
按热度按时间lsmd5eda1#
首先,您所说的所有导致错误的示例在参考实现(来自JDK 8的javac)中编译良好。它们在IntelliJ中也工作良好,因此您看到的错误很可能是Eclipse特有的。
你潜在的问题似乎是:“为什么当我开始链接时它停止工作?”原因是,当lambda表达式和泛型方法调用作为方法参数出现时,它们是 *poly表达式 *(它们的类型是上下文相关的),而当它们作为方法接收器表达式出现时,它们不是。
当你说
存在足够的类型信息来求解
comparing()
的类型自变量和自变量类型p1
。comparing()
调用从Collections.sort
的签名获得其目标类型,因此已知comparing()
必须返回Comparator<Song>
,并且因此p1
必须是Song
。但是当你开始链接时:
现在我们遇到了一个问题,我们知道复合表达式
comparing(...).thenComparing(...)
有一个目标类型Comparator<Song>
,但是因为链的接收方表达式comparing(p -> p.getTitle())
是一个泛型方法调用,我们不能从它的其他参数推断出它的类型参数,我们有点不走运,因为我们不知道这个表达式的类型,我们不知道它有thenComparing
方法等等。有几种方法可以解决这个问题,所有这些方法都涉及注入更多类型信息,以便链中的初始对象可以被正确地类型化。下面是这些方法,大致按照需求性降低和侵入性增加的顺序排列:
Song::getTitle
。这样就提供了足够的类型信息来推断comparing()
调用的类型变量,从而为它提供了一个类型,并继续向下。comparing()
调用提供类型见证:Comparator.<Song, String>comparing(...)
.Comparator<Song>
,为显式目标类型提供强制转换。ia2d9nvy2#
问题出在类型推理上,如果没有在第一个比较中添加
(Song s)
,comparator.comparing
就不知道输入的类型,所以默认为Object。您可以通过以下3种方法之一解决此问题:
1.使用新的Java 8方法引用语法
1.将每个比较步骤提取到本地引用中
1.强制使用Comparator返回的类型(注意,输入类型和比较键类型都需要)
我认为"last"
thenComparing
语法错误误导了你,它实际上是整个链的类型问题,只是编译器只将链的末尾标记为语法错误,因为我猜这是最终返回类型不匹配的时候。我不知道为什么
List
比Collection
做得更好,因为它应该做相同的捕获类型,但显然不是。8dtrkrch3#
处理此编译时错误的另一种方法:
显式地转换你的第一个比较函数的变量,然后就可以开始了。我已经对org.bson.documents对象的列表进行了排序。请看示例代码
tv6aics14#
playlist1.sort(...)
根据playlist1的声明为类型变量E创建Song的边界,其“波及”到比较器。在
Collections.sort(...)
中,没有这样的界限,从第一个比较器的类型推断不足以让编译器推断其余的比较器。我想你会从
Collections.<Song>sort(...)
中得到“正确”的行为,但是没有安装java8来测试它。