当前教程
组合 Lambda 表达式
系列中的下一篇

系列中的上一篇: 将 Lambda 表达式编写为方法引用

系列中的下一篇: 编写和组合比较器

组合 Lambda 表达式

您可能已经注意到 java.util.function 包中的函数式接口中存在默认方法。这些方法的添加是为了允许组合和链接 Lambda 表达式。

为什么要这样做?简单来说,是为了帮助您编写更简洁、更易读的代码。

 

使用默认方法链接谓词

假设您需要处理一个字符串列表,只保留非空、非空且长度小于 5 个字符的字符串。问题的陈述方式如下。您对给定字符串有三个测试

  • 非空;
  • 非空;
  • 长度小于 5 个字符。

每个测试都可以用一个非常简单的单行谓词轻松编写。也可以将这三个测试组合成一个单一的谓词。它将看起来像这段代码

Predicate<String> p = s -> (s != null) && !s.isEmpty() && s.length() < 5;

但 JDK 允许您以这种方式编写这段代码

Predicate<String> nonNull = s -> s != null;
Predicate<String> nonEmpty = s -> !s.isEmpty();
Predicate<String> shorterThan5 = s -> s.length() < 5;

Predicate<String> p = nonNull.and(nonEmpty).and(shorterThan5);

隐藏技术复杂性并暴露代码意图是组合 Lambda 表达式的目的。

这段代码在 API 级别是如何实现的?不深入细节,您可以看到以下内容

由于函数式接口只允许一个抽象方法,因此此 and() 方法必须是默认方法。因此,从 API 设计的角度来看,您拥有创建此方法所需的所有元素。好消息是:Predicate<T> 接口有一个 and() 默认方法,因此您无需自己创建它。

顺便说一下,还有一个 or() 方法,它接受另一个谓词作为参数,还有一个 negate() 方法,它不接受任何参数。

使用这些方法,您可以用这种方式编写前面的示例

Predicate<String> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNullOrEmpty = isNull.or(isEmpty);
Predicate<String> isNotNullNorEmpty = isNullOrEmpty.negate();
Predicate<String> shorterThan5 = s -> s.length() < 5;

Predicate<String> p = isNotNullNorEmpty.and(shorterThan5);

即使这个例子可能有点过分,但您可以通过利用方法引用和默认方法来显著提高代码的可表达性。

 

使用工厂方法创建谓词

通过使用函数式接口中定义的工厂方法,可以进一步提高可表达性。在 Predicate<T> 接口上,有两个这样的方法。

在下面的示例中,谓词 isEqualToDuke 测试一个字符字符串。当测试的字符串等于 "Duke" 时,测试结果为真。此工厂方法可以为任何类型的对象创建谓词。

Predicate<String> isEqualToDuke = Predicate.isEqual("Duke");

第二个工厂方法会否定作为参数给出的谓词。

Predicate<Collection<String>> isEmpty = Collection::isEmpty;
Predicate<Collection<String>> isNotEmpty = Predicate.not(isEmpty);

 

使用默认方法链接消费者

The Consumer<T> 接口也有一个方法来链接消费者。您可以使用以下模式链接消费者

Logger logger = Logger.getLogger("MyApplicationLogger");
Consumer<String> log = message -> logger.info(message);
Consumer<String> print = message -> System.out.println(message);

Consumer<String> longAndPrint = log.andThen(print);

在这个例子中,printAndLog 是一个消费者,它会先将消息传递给 log 消费者,然后传递给 print 消费者。

 

使用默认方法链接和组合函数

链接和组合之间的区别有点微妙。实际上,两种操作的结果都是一样的。不同的是编写方式。

假设您有两个函数 f1f2。您可以通过调用 [f1.andThen(f2) 来链接它们。将生成的函数应用于一个对象,会先将此对象传递给 f1,然后将结果传递给 f2

Function 接口还有一个第二个默认方法:f2.compose(f1)。以这种方式编写,生成的函数会先将对象传递给 f1 函数进行处理,然后将结果传递给 f2

您需要了解的是,为了获得相同的生成函数,您需要在 f1 上调用 andThen() 或在 f2 上调用 compose()

您可以链接或组合不同类型的函数。不过,存在明显的限制:f1 生成的结果类型应与 f2 消费的类型兼容。

 

创建恒等函数

The Function<T, R> 接口还有一个工厂方法来创建恒等函数,称为 identity()

因此,可以使用以下简单的模式创建恒等函数

Function<String, String> id = Function.identity();

此模式适用于任何有效的类型。


上次更新: 2021 年 10 月 26 日


当前教程
组合 Lambda 表达式
系列中的下一篇

系列中的上一篇: 将 Lambda 表达式编写为方法引用

系列中的下一篇: 编写和组合比较器