当前位置: 首页 > 知识库问答 >
问题:

被Java 8比较器类型推断弄糊涂了

萧麒
2023-03-14

我一直在研究集合之间的差异。排序和列表。排序,特别是关于使用比较器静态方法以及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()));

一旦我开始链接thenComping,就会发生以下情况:

html" target="_blank">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.语法错误,因为它不再知道p1的类型。为了解决这个问题,我在第一个参数(比较)中添加了typeSong

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())
        );

现在是令人困惑的部分。对于playlist1。排序,即列表,这将解决以下调用的所有编译错误,然后比较调用。但是,对于集合。排序,它对第一个进行排序,但对最后一个进行排序。我在测试中添加了几个额外的调用,然后进行比较,最后一个调用总是显示错误,除非我将参数设置为(Song p1)

现在,我通过创建树集和使用对象进一步测试了这一点。比较:

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())
            );

与中发生的情况相同,对于树集,除了对象之外,没有编译错误。比较最后一次调用,然后比较显示错误。

有人能解释为什么会发生这种情况吗,以及为什么在简单地调用比较方法时根本不需要使用(Song p1)(没有进一步的thenComping调用)。

关于同一主题的另一个查询是当我对TreeSet执行此操作时:

Set<Song> set = new TreeSet<Song>(
            Comparator.comparing(p1 -> p1.getTitle())
            .thenComparing(p1 -> p1.getDuration())
            .thenComparing(p1 -> p1.getArtist())
            );

i、 e.从comparing方法调用的第一个lambda参数中删除typeSong,它显示了对comparing的调用和对然后比较的第一个调用下的语法错误,但没有显示对然后比较的最后一个调用-几乎与上述情况相反!然而,对于所有其他3个示例,即使用<代码>对象。比较列表。对集合进行排序。排序当我删除第一个歌曲参数类型时,它会显示所有调用的语法错误。

非常感谢。

编辑以包括我在Eclipse KeplerSR2中收到的错误截图,我现在发现这些错误是Eclipse特定的,因为在命令行上使用JDK8 java编译器编译时,它编译正常。

共有3个答案

赵智
2023-03-14

处理此编译时错误的另一种方法:

显式转换第一个比较函数的变量,然后就可以了。我已经对组织列表进行了排序。bson公司。文档对象。请查看示例代码

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());
方飞白
2023-03-14

问题是类型推断。无需在第一次比较中添加(Song s)比较器。比较不知道输入的类型,所以默认为Object。

您可以通过以下3种方法解决此问题:

>

  • 使用新的Java8方法引用语法

     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())
                );
    

    我认为“最后”thenComping语法错误正在误导您。这实际上是整个链的类型问题,只是编译器只将链的末尾标记为语法错误,因为我猜那是最终返回类型不匹配的时候。

    我不知道为什么列表比收集做得更好,因为它应该做相同的捕获类型,但显然不是。

  • 夏理
    2023-03-14

    首先,您所说的所有导致错误的示例都可以使用参考实现(来自JDK 8的javac)进行良好编译它们在IntelliJ中也可以正常工作,因此您看到的错误很可能是特定于Eclipse的。

    你潜在的问题似乎是:“当我开始链接时,为什么它会停止工作?”原因是,当lambda表达式和泛型方法调用显示为方法参数时,它们是多边形表达式(其类型是上下文敏感的),而当它们显示为方法接收方表达式时,它们不是。

    当你说

    Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
    

    有足够的类型信息来解决比较()的类型参数和参数类型p1比较()调用从Collections.sort的签名中获取其目标类型,因此已知比较()必须返回比较器

    但当您开始链接时:

    Collections.sort(playlist1,
                     comparing(p1 -> p1.getTitle())
                         .thenComparing(p1 -> p1.getDuration())
                         .thenComparing(p1 -> p1.getArtist()));
    

    现在我们有问题了。我们知道复合表达式<代码>比较(…)。然后比较(…)的目标类型为Comparator

    有几种方法可以解决这个问题,所有这些方法都涉及注入更多类型信息,以便可以正确键入链中的初始对象。以下是它们,按降低可取性和增加侵入性的粗略顺序排列:

    • 使用一个精确的方法引用(一个没有重载的引用),比如宋::getTitle。然后,这将提供足够的类型信息来推断comparing()调用的类型变量,从而为其指定一个类型,从而继续整个链
    • 使用显式lambda(正如您在示例中所做的那样)
    • 为调用比较器提供类型见证。
     类似资料:
    • 主要内容:1 Java8 类型推断的介绍,2 Java8 类型推断的案例1,3 Java8 类型推断的案例21 Java8 类型推断的介绍 类型推断是Java的一项功能,它使编译器可以查看每个方法调用和相应的声明以确定参数的类型。 Java在Java 8中提供了类型推断的改进版本。 1.1 Java8以前 在下面的声明中,我们在一侧提到了arraylist的类型。这种方法是在Java 7中引入的。在这里,您可以将第二面留为<>,并且编译器将通过引用变量的类型来推断其类型。 1.2 Java8以后

    • 我正在尝试学习如何使用他们网站上的“入门”教程使用丢弃向导构建REST API: https://www.dropwizard.io/en/stable/getting-started.html 我对为了使程序正常工作而必须创建的所有类的目的感到非常困惑。本教程在某种程度上解释了这些类,但我发现解释非常模糊和神秘。有人可以用通俗的话向我解释每个课程的目的是什么吗? 配置类 应用程序类 表示类 资源

    • 我有以下课程: 这编译没有问题。但是,如果我尝试像这样内联变量: 我发现以下编译错误: 如何编写此表达式以使Java正确理解参数?

    • 我本想把它添加到另一个线程中,但我无法评论其他帖子。我读到的没有回答我的问题。我刚刚安装了EAP 7.2.0。GA。在控制台日志中,它说: 然而,其他人认为它是在第13版左右。当我看野蝇(http://wildfly.org/downloads/)的版本时,第6版太老了,甚至没有出现,应该是在2014年之前... 那么,它怎么可能是6.0.11呢。最终的

    • 我想用Lambda对列表排序: 但我得到了这个编译错误:

    • 假设我有一个双人课 我希望对它进行排序,首先是第一个值,然后是第二个值。现在,如果我这样做 一切都很好,列表按对的第一个值排序,但如果我这样做 它因错误而失败 好吧,所以它可能无法推断参数,所以如果我这样做 它因错误而失败 为什么它适用于comparing()而不适用于comparing()。然后比较()?