【问题标题】:Java 8 Comparator with Method reference带有方法参考的 Java 8 比较器
【发布时间】:2019-07-21 23:10:48
【问题描述】:

试图理解方法引用,但很难解决它。 卡住了比较器的例子。

为了让我的大脑更轻松,我一直从基础开始(匿名类实现>>兰巴表达式>>方法参考) 我无法使用方法引用和 Comparator 示例。

我想要排序的 POJO(从一些互联网示例中提取 - mkyong)

public class Fruit{
   private int quantity;
   private String name;
   ....
}

创建要使用的测试数据:

Fruit[] fruits = new Fruit[3];
Fruit pineapple = new Fruit("pineapple",10);
Fruit orange= new Fruit("orange",4);
Fruit kiwi= new Fruit("kiwi",15);

fruits[0] = pineapple;
fruits[1] = orange;
fruits[2] = kiwi;

匿名排序类实现:

Comparator<Fruit> anonymousSortByname = new Comparator<Fruit>(){
     public int compare(Fruit f1,Fruit f2){
        return f1.getName().compareTo(f2.getName());
     }
};

排序:

Arrays.sort(fruits,anonymousSortByname);

现在我明白了,匿名类可以转换为 lambda 表达式(只要它们是函数式接口等......)

Lambda 实现:

Comparator<Fruit> lambdaSortByname = 
 (Fruit f1,Fruit f2) -> f1.getName().compareTo(f2.getName());

好的,到目前为止,事情是有道理的。

我的理解:“无论在何处使用 lambda,它们可以替换为‘方法引用’”

确实在 SO 和互联网上阅读到,使用“方法引用”不是强制性的,但我的目标是理解它们 - 所以是否使用不是我的问题 :)

也在这里阅读: method reference1herehere 。 但是,当我尝试使用 Comparator 实现方法引用时,我遇到了困难。

这是我尝试过的,它给了我一个编译错误。

Comparator<Fruit> methodRefComparator = Comparator::compare;

编译错误:

无法对非静态代码块进行静态引用

在链接中,该示例确实讨论了 Comparator 接口中的“比较”方法,但此时变得模糊......

编辑#1

根据下面的cmets修改了问题:

困惑在于 Comparator 的“comparing”方法是如何工作的。

我确实得到了一个工作示例(通过谷歌搜索而不是根据我自己的理解):

首先创建了一个接受“水果”作为输入并返回字符串的函数。

Function<Fruit, String> funcName = (Fruit f1) -> f1.getName();

然后将此“函数”传递给“比较器”的“比较”方法,以获取所需的比较器。

Comparator<Fruit> sortByName = Comparator.comparing(funcName);

现在排序工作:

Arrays.sort(fruits,sortByName );

但是..我无法理解它是如何工作的。 我没有提供 Comparator 的实现(f1.getName() - f2.getName())那么它是如何工作的呢?

这更像是一个合同,编译器能够理解,因为我已经为它提供了一个带有“getName”实现的函数,它将在返回的比较器中使用它?

【问题讨论】:

    标签: lambda java-8 method-reference


    【解决方案1】:

    Comparator.comparing 是一个 static 工厂方法,它创建一个比较器,该比较器将委托给您提供的函数,用于获取要比较的属性值。基本上是这样的:

    public static <T, U extends Comparable<? super U>> 
        Comparator<T> comparing(Function<? super T, ? extends U> func) {
    
        return (o1, o2) -> func.apply(o1).compareTo(func.apply(o2));
    }
    

    因此,为两个参数评估函数并使用结果调用 compareTo 的逻辑就在那里。

    由于这对于所有比较元素属性的比较器来说都是通用的,因此将通用代码放入共享方法中是很自然的决定。这种分离也简化了你必须提供的功能,因为它只需要表达,如何获取属性值。

    由于这通常意味着仅调用单个(getter)方法,因此它允许将函数表达为方法引用,这仅在存在单个现有方法执行您的函数应该执行的操作时才有可能,即

    Arrays.sort(fruits, Comparator.comparing(Fruit::getName));
    

    其中Fruit::getName 等价于(Fruit f) -&gt; f.getName()

    因此,并非每个 lambda 表达式都可以替换为方法引用。当 lambda 表达式由单个方法调用组成时,或者当实用程序方法帮助您将您这边的逻辑简化为由单个方法调用组成的函数时,您可以替换它。

    【讨论】:

    • 感谢您提供详细的解释。如果我将 Function 实现提供为 Fruit::getQty ,它似乎也可以工作。在常规实现中,我会将其写为 f1.getQty() - f2.getQty 但“比较”实现会执行 compareTo 。对这一点感到困惑
    • 你不应该在比较器实现中使用减号。它可能适用于某些受限的值范围,但通常,减号可能会溢出。除此之外,当您将int 返回方法绑定到Function 时,它将使用自动装箱,即您获得Function&lt;Fruit,Integer&gt; 并且通用comparing 方法可以返回一个函数,该函数将在@ 上调用compareTo 987654334@ 对象。您可以改用comparingInt(ToIntFunction) 来避免装箱。然后,它将执行(Fruit f1, Fruit f2) -&gt; Integer.compare(f1.getQty(), f2.getQty()),这也是您应该执行的操作,而不是使用减号。
    【解决方案2】:

    当你使用 -> 时,你可以在函数中使用变量。 当你使用 :: 时,函数使用默认变量。

    您的源代码编译失败,因为函数比较是用 2 个变量实现的,而您被放入 0 个变量。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      • 2015-07-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-26
      相关资源
      最近更新 更多