整合Lambda表达式

从函数式接口中利用默认和静态方法

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

你为什么要这么做?只是为了帮助您编写更简单、更可读的代码。

使用默认方法链接Predicates

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

  • 非null;
  • 非空;
  • 少于5个字符。

每个测试都可以很容易地用非常简单的一行predicate编写。还可以将这三个测试合并到一个predicate中。它看起来像下面的代码:

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()是一个方法
  • 它是在Predicate的实例上调用的:因此它是一个实例方法
  • 它接受另一个Predicate作为参数
  • 它返回一个Predicate

因为函数接口只允许一个抽象方法,所以这个and()方法必须是默认方法。因此,从API设计的角度来看,您拥有创建此方法所需的所有元素。好消息是:Predicate接口有一个and()默认方法,所以您不必自己编写。

顺便说一下,还有一个or()方法接受另一个Predicate作为参数,还有一个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);

即使这个示例可能把极限推得有点过了,您也可以通过利用方法引用和默认方法来显著提高代码的表达能力。
使用工厂方法创建Predicates
通过使用在函数式接口中定义的工厂方法,可进一步推进表达性。在Predicate接口上有两个。

在下面的示例中,Predicates isEqualToDuke测试一个字符串。当被测试的字符串等于“Duke”时,测试为true。这个工厂方法可以为任何类型的对象创建Predicates 。

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

第二个工厂方法取反作为参数的predicate 。

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

使用默认方法链接Consumers
Consumer接口还有一个链接consumers的方法。您可以使用以下模式链接consumers:

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

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

在本例中,printAndLog是一个消费者,它首先将消息传递给日志消费者,然后再传递给打印消费者。

使用默认方法链接和组合Functions

链接和组合之间的区别有点微妙。这两种操作的结果实际上是相同的。不同的是你写的方式。

假设你有两个Function f1和f2。你可以通过调用[f1.andThen(f2)来链接它们。将结果函数应用到一个对象将首先将这个对象传递给f1,并将结果传递给f2。

Function接口有第二个默认方法:f2.compose(f1)。以这种方式编写,结果函数将首先传递对象给f1函数,然后将结果传递给f2。

您需要认识到的是,为了得到相同的结果函数,您需要在f1上调用andThen()或在f2上调用compose()。

您可以链接或组合不同类型的Function。但是有明显的限制:f1产生的结果的类型应该与f2使用的类型兼容。

创建Identity函数

Function<T, R>接口还有一个用于创建标识函数的工厂方法,称为identity()。

因此,可以使用以下简单模式创建标识函数:

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

此模式适用于任何有效类型。(Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t形式的Lambda表达式)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值