Java 8比较器类型推论非常困惑

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


这些是我的发现。假设我们有一个ArrayListtype 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") );

这是对两种有效的排序方法的调用,没问题:


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到(比较)的第一个参数中:

现在是混淆部分。对于p laylist1.sort,即List,这可以解决以下两个thenComparing调用的所有编译错误。但是,对于Collections.sort,它将为第一个解决,而不是最后一个。我测试过添加了一些额外的调用thenComparing,除非我(Song p1)输入了参数,否则它总是显示最后一个错误。


与中发生的情况相同,对于TreeSet,没有编译错误,但Objects.compare最后一次调用thenComparing显示错误。


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


关于同一主题的另一个查询是我对以下内容进行查询时TreeSet:


Set<Song> set = new TreeSet<Song>(

            Comparator.comparing(p1 -> p1.getTitle())

            .thenComparing(p1 -> p1.getDuration())

            .thenComparing(p1 -> p1.getArtist())

            );

例如,Song从用于比较方法调用的第一个lambda参数中删除类型,它显示在进行比较的调用和对的第一个调用(thenComparing但对最终调用没有)下的语法错误thenComparing-几乎与上述情况相反!然而,所有其他3个例子,即有Objects.compare,List.sort而且Collections.sort当我删除第一个Song参数类型它显示语法错误的所有电话。


提前谢谢了。


经过编辑后包含了我在Eclipse Kepler SR2中收到的错误的屏幕截图,此后我现在发现它们是特定于Eclipse的,因为在命令行上使用JDK8 java编译器进行编译时,它可以编译。

http://img.mukewang.com/5dca56ce0001b59a06900652.jpg

白衣染霜花
浏览 523回答 3
3回答

holdtom

首先,您说的所有示例均会导致错误使用参考实现(来自JDK 8的javac)进行编译。它们在IntelliJ中也能正常运行,因此您看到的错误很可能是特定于Eclipse的。您的基本问题似乎是:“为什么当我开始链接时它会停止工作。” 原因是,当lambda表达式和通用方法调用作为方法参数出现时是多义表达式(它们的类型是上下文相关的),而当它们作为方法接收者表达式出现时却不是。当你说Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));有足够的类型信息可以解决的类型参数comparing()和参数类型p1。该comparing()调用从的签名获取其目标类型Collections.sort,因此已知该调用comparing()必须返回Comparator<Song>,因此p1必须为Song。但是,当您开始链接时:Collections.sort(playlist1,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;comparing(p1 -> p1.getTitle())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;.thenComparing(p1 -> p1.getDuration())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;.thenComparing(p1 -> p1.getArtist()));现在我们有一个问题。我们知道复合表达式comparing(...).thenComparing(...)的目标类型为Comparator<Song>,但是由于链的接收方表达式comparing(p -> p.getTitle())是通用方法调用,并且我们无法从其他参数推断其类型参数,因此我们有点不走运。由于我们不知道此表达式的类型,因此我们不知道它具有thenComparing方法,等等。有几种方法可以解决此问题,所有方法都涉及注入更多类型信息,以便可以正确键入链中的初始对象。在这里,它们按照减少期望和增加干扰的大致顺序排列:使用精确的方法引用(没有重载的方法引用),例如Song::getTitle。然后,这会提供足够的类型信息以推断出该comparing()调用的类型变量,从而为其提供类型,并因此继续进行下去。使用显式lambda(如您在示例中所做的那样)。提供comparing()呼叫的见证人:Comparator.<Song, String>comparing(...)。通过将接收者表达式强制转换为,为显式目标类型提供强制类型转换Comparator<Song>。

胡说叔叔

playlist1.sort(...) 从播放列表1的声明为类型变量E创建Song的界限,该声明“波纹”到比较器。在中Collections.sort(...),没有这样的界限,并且从第一个比较器的类型进行的推断不足以使编译器推断出其余的。我认为您会从中获得“正确”的行为Collections.<Song>sort(...),但是没有安装Java 8可以为您进行测试。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java