将 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::length
或 User::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);
两个变量 newListOfStrings
和 newListOfNStrings
都可以使用相同的语法 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 日