为什么lambda表达式可以用作比较器?

问题描述 投票:7回答:9

在“OCP学习指南”一书中,有一个关于可以用两种方式初始化的比较器的示例。第一个是通过这样的匿名类:

Comparator<Duck> byWeight = new Comparator<Duck>(){
    public int compare(Duck d1, Duck d2){
        return d1.getWeight() - d2.getWeight();
    }
};

这个我能理解。根据这本书,这可以用这样的lambda表达式替换:

Comparator<Duck> byWeight = (d1,d2) -> d1.getWeight() - d2.getWeight();

现在这个我不明白。 lambda表达式不返回Comparator对象,由于Comparator是一个接口,因此我现在无法想到它。

那么第一个例子中的new运算符是否指的是正在制作的匿名类,它被称为Comparator,因为匿名类实现了Comparator接口?

那么在例2中到底发生了什么?是否以lambda表达式创建了某个对象?在这个例子中,你使用byWeight作为参考变量吧?

我真的不明白这一点,有人可以解释一下吗?谢谢。

java lambda java-8 comparator anonymous-inner-class
9个回答
11
投票

如果您阅读documentation of the Comparator interface,您可以阅读:

功能接口:这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。

因此Comparator<T>界面是implemented like

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

    // ...

}

现在,如果我们查看@FunctionalInterface的文档,我们会看到:

一种信息性注释类型,用于指示接口类型声明旨在成为Java语言规范定义的功能接口。从概念上讲,功能界面只有一种抽象方法。由于默认方法具有实现,因此它们不是抽象的。如果接口声明覆盖java.lang.Object的公共方法之一的抽象方法,那么也不会计入接口的抽象方法计数,因为接口的任何实现都将具有来自java.lang.Object或其他地方的实现。

所以基本上如果你有一个抽象方法的接口,并且你将接口注释为@FunctionalInterface,那么该接口是函数的目标:你或多或少构造一个实现功能接口的匿名类和你指定的函数是唯一抽象方法的实现。

换句话说,表达式:

Comparator<Duck> byWeight = <somelambda>

相当于:

Comparator<Duck> byWeight = new Comparator<Duck>(){
    public int compare(Duck d1, Duck d2){
        return <somelambda>(d1,d2);
    }
}

3
投票

在第一个代码块中,创建的对象实例实现了Comparator<Duck>,但相应的类没有名称(是匿名的)。

在第二个代码块中,同样的事情发生了。因为Comparator接口只定义了一个方法(名为compare),所以可以使用lambda表达式缩写接口(匿名)实现的创建。

在两个示例中,变量byWeight可以以相同的方式使用。在任何地方都需要Comparator<Duck>,可以使用byWeight - 它对应于变量的类型信息。在内部,只要在此实现上调用compare,就会使用使用lambda表达式提供的定义。


2
投票

在Java 8中,Comparator<T>@FunctionalInterface注释。它的文件说:

一种信息性注释类型,用于指示接口类型声明旨在成为Java语言规范定义的功能接口。从概念上讲,功能界面只有一种抽象方法。由于默认方法具有实现,因此它们不是抽象的。如果接口声明了一个覆盖java.lang.Object的公共方法之一的抽象方法,那么它也不会计入接口的抽象方法计数,因为接口的任何实现都将具有java.lang.Object或其他地方的实现。

请注意,可以使用lambda表达式,方法引用或构造函数引用创建功能接口的实例。

如果使用此批注类型对类型进行批注,则编译器需要生成错误消息,除非:

类型是接口类型,而不是注释类型,枚举或类。带注释的类型满足功能接口的要求。但是,无论接口声明中是否存在FunctionalInterface注释,编译器都会将满足功能接口定义的任何接口视为功能接口。

这里最重要的部分是instances of functional interfaces can be created with lambda expressions, method references, or constructor references.,它可以回答你的问题。


2
投票

比较器基本上只是一个函数,它接受两个参数并返回一个int。

实际上,这里发生的是编译器能够巧妙地推断出右侧需要的是什么,因为你声明了左侧。

Comparator<Duck> byWeight = (d1,d2) -> d1.getWeight() - d2.getWeight();
                           //^   ^ I know a Comparator<Duck> takes two Ducks.
                                      // ^ I know a Comparator<Duck> returns an int

这是可能的,因为Comparator<T>被定义为functional interface

这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。


2
投票

接口Comparator是一个功能接口,这意味着这个接口只能包含一个抽象方法。

然后你可以使用lambda表达式来定义这个抽象方法的实现,基本上(d1,d2) -> d1.getWeight() - d2.getWeight();是抽象方法int compare(T o1, T o2);的实现。

由于功能接口只包含一个抽象方法,因此可以使用lambda表达式来定义此类接口的实现


1
投票

Lambda表达式只是一个功能接口(只有一个函数的接口)的简写,你不需要在(中写入参数列表你的parameterListHere )再写->,然后写这个怎么办/ return(即函数体)。你也可以用{ }写它

Comparator<Duck> byWeight = (d1,d2) -> { d1.getWeight() - d2.getWeight(); }

1
投票

您可以将lambda视为不属于类的方法,它可以像一段数据一样传递。

或者,通过非常小的心理转变,您可以将其视为只有一种方法的对象。

有一组标记为功能接口的接口。这在Javadoc中提到:

功能界面:

这是一个功能接口,因此可以用作lambda表达式或方法引用的赋值目标。

这是一种技术方式,因为它们只有一种方法,并且它们被标记为功能,编译器可以将lambda视为具有该接口的对象。它知道使用lambda作为接口中一个方法的实现。

所以你可以这样做:

Comparator<Pet> byNameComparator = (f1,f2) -> f1.name().compareTo(f2.name());
Predicate<Customer> isVip = cust -> cust.orderCount > 20;
Callable<Item> getResult = () -> queue.getItem();
Function<Integer,Integer> multiply = (a,b) -> a * b;

... 等等。

无论参数的类型是函数接口,您都可以直接使用lambda,因此给出:

 public void sort(Comparator cmp);

...你可以称之为:

 foo.sort( (a,b) -> a.name().compareTo(b.name()));

1
投票

在第一种方式中,您将创建一个新的匿名类(具有行为的方法)。

在第二种方式中,您只是暴露行为(认为这是一种共享函数的方法,一种方法,即使它是透明创建的,也不会看到周围的类)。

我记得在Java Tutorial - The Lambda Expressions中已经清楚地解释过了

匿名类的一个问题是,如果匿名类的实现非常简单,例如只包含一个方法的接口,那么匿名类的语法可能看起来不实用且不清楚。在这些情况下,您通常会尝试将功能作为参数传递给另一个方法,例如当有人单击按钮时应采取的操作。 Lambda表达式使您可以执行此操作,将功能视为方法参数,或将代码视为数据。

我建议你集中注意力,使用lambdas,你只是试图将行为暴露给某个类或组件,你的例子可能是Collections.sort

lambdas让你有机会有一个更清晰简单的表达式,避免匿名类声明的样板。


1
投票

基本上lambdas的行为与内部类非常相似,但允许使用更好的语法,而且开销更少。

使用Java8 Comparator是一个@FunctionalInterfaceJavaDoc),它可以使用lambda表达式。也就是说,您可以使用返回类型int定义Bi-Function(具有两个参数的函数)并将其用作Comparator,这在第二个示例中完成。两个Comparator实例可以在剩余代码中以完全相同的方式使用。

© www.soinside.com 2019 - 2024. All rights reserved.