当前教程
将 Lambda 表达式写成方法引用
系列中的下一篇

系列中的上一篇: 在您的应用程序中使用 Lambda 表达式

系列中的下一篇: 组合 Lambda 表达式

将 Lambda 表达式写成方法引用

您已经看到,Lambda 表达式实际上是方法的实现:函数式接口中唯一的抽象方法。有时人们将这些 Lambda 表达式称为“匿名方法”,因为它正是这样:一个没有名称的方法,您可以将其在应用程序中移动、存储在字段或变量中、作为参数传递给方法或构造函数,以及从方法中返回。

有时您会编写 Lambda 表达式,它们只是对在其他地方定义的特定方法的调用。实际上,您在编写以下代码时已经这样做了

Consumer<String> printer = s -> System.out.println(s);

这样写,这个 Lambda 表达式只是对 println() 方法的引用,该方法在 System.out 对象上定义。

这就是方法引用语法发挥作用的地方。

 

您的第一个方法引用

有时 Lambda 表达式只是对现有方法的引用。在这种情况下,您可以将其写成方法引用。前面的代码将变成以下代码

Consumer<String> printer = System.out::println;

方法引用有四类

  • 静态方法引用
  • 绑定方法引用
  • 未绑定方法引用
  • 构造函数方法引用

printer 消费者属于未绑定方法引用类别。

大多数情况下,您的 IDE 能够告诉您特定 Lambda 表达式是否可以写成方法引用。不要犹豫,问问它!

 

编写静态方法引用

假设您有以下代码

DoubleUnaryOperator sqrt = a -> Math.sqrt(a);

这个 Lambda 表达式实际上是对静态方法 Math.sqrt() 的引用。它可以这样写

DoubleUnaryOperator sqrt = Math::sqrt;

这个特定的方法引用指的是静态方法,因此被称为静态方法引用。静态方法引用的通用语法是 RefType::staticMethod

静态方法引用可以接受多个参数。考虑以下代码

IntBinaryOperator max = (a, b) -> Integer.max(a, b);

您可以使用方法引用将其改写

IntBinaryOperator max = Integer::max;

 

编写未绑定方法引用

不接受任何参数的方法

假设您有以下代码

Function<String, Integer> toLength = s -> s.length();

此函数可以写成 ToIntFunction<T>。它只是对 length() 方法的引用,该方法属于 String 类。因此您可以将其写成方法引用

Function<String, Integer> toLength = String::length;

此语法可能一开始会让人困惑,因为它看起来确实像静态调用。但实际上并非如此:length() 方法是 String 类的实例方法。

您可以使用此类方法引用调用普通 Java Bean 中的任何 getter。假设您有一个 User 类,其中定义了 getName()。然后您可以编写以下函数

Function<User, String> getName = user -> user.getName();

作为以下方法引用

Function<String, Integer> getName = User::getName;

不接受任何参数的方法

以下是一个您已经见过的另一个示例

BiFunction<String, String, Integer> indexOf = (sentence, word) -> sentence.indexOf(word);

这个 Lambda 实际上是对 indexOf() 方法的引用,该方法属于 String 类,因此可以写成以下方法引用

BiFunction<String, String, Integer> indexOf = String::indexOf;

此语法可能比更简单的示例 String::lengthUser::getName 更令人困惑,这些示例非常直观。在脑海中重构以经典方式编写的 Lambda 的一种好方法是检查此方法引用的类型。这将告诉您此 Lambda 接受的参数。

未绑定方法引用的通用语法如下:RefType:instanceMethod,其中 RefType 是类型的名称,instanceMethod 是实例方法的名称。

 

编写绑定方法引用

您看到的第一个方法引用示例如下

Consumer<String> printer = System.out::println;

此方法引用称为绑定方法引用。此方法引用被称为绑定,因为调用方法的对象在方法引用本身中定义。因此,此调用绑定到方法引用中给出的对象。

如果您考虑未绑定语法:Person::getName,您会发现调用方法的对象不属于此语法:它作为 Lambda 表达式的参数提供。考虑以下代码

Function<User, String> getName = User::getName;
User anna = new User("Anna");
String name = getName.apply(anna);

您可以看到,该函数应用于 User 的特定实例,该实例传递给函数。然后,此函数对该实例进行操作。

在前面的消费者示例中并非如此:println() 方法调用的是 System.out 对象,该对象是方法引用的一部分。

绑定方法引用的通用语法如下:expr:instanceMethod,其中 expr 是返回对象的表达式,instanceMethod 是实例方法的名称。

 

编写构造函数方法引用

您需要了解的最后一种方法引用是构造函数方法引用。假设您有以下 Supplier<List<String>>

Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();

您可以看到,与其他方法一样:这归结为对 ArrayList 的空构造函数的引用。好吧,方法引用可以做到这一点。但由于构造函数不是方法,因此这是方法引用的另一类。语法如下

Supplier<List<String>> newListOfStrings = ArrayList::new;

您会注意到,这里不需要菱形运算符。如果您想使用它,那么您还需要提供类型

Supplier<List<String>> newListOfStrings = ArrayList<String>::new;

您需要注意的是,如果您不知道方法引用的类型,那么您无法确切地知道它做了什么。以下是一个示例

Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
Function<Integer, List<String>> newListOfNStrings = size -> new ArrayList<>(size);

两个变量 newListOfStringsnewListOfNStrings 都可以使用相同的语法 ArrayList::new 编写,但它们不引用同一个构造函数。您只需要注意这一点。

 

总结方法引用

以下是四种方法引用类型。

名称 语法 Lambda 等效项
静态 RefType::staticMethod (args) -> RefType.staticMethod(args)
绑定 expr::instanceMethod (args) -> expr.instanceMethod(args)
未绑定 RefType::instanceMethod (arg0, rest) -> arg0.instanceMethod(rest)
构造函数 ClassName::new (args) -> new ClassName(args)

上次更新: 2021 年 10 月 26 日


当前教程
将 Lambda 表达式写成方法引用
系列中的下一篇

系列中的上一篇: 在您的应用程序中使用 Lambda 表达式

系列中的下一篇: 组合 Lambda 表达式