复合Lambda 表达式的有用方法
Java8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法。
这是什么意思呢?
在实践中,这意味着你可以把多个简单的Lambda复合成为复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成为了一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。
你可能会在想,函数式接口中怎么可能会有更多的方法呢?(毕竟,这违背了函数式接口的定义啊!)。关键在于,我们即将介绍的方法都是默认方法,也就是说它们不是抽象方法。
比较器复合
我们前面看到了,你可以使用Comparator.comparing,根据提取用于比较的键值的Function来返回一个Comparator对象,如下所示:
inventory.sort(Comparator.comparing(Apple::getWeight));
A.逆序
如果你想要对苹果按质量递减排序怎么办?
用不着去建立另一个Comparator的实例。接口有一个默认方法reversed可以使给定的比较器排序。
因此仍然用开始的那个比较器,只要修改一下前一个例子就可以对苹果按重量递减排序:
inventory.sort(Comparator.comparing(Apple::getWeight).reversed());
B.比较器链
上面说的都好,但是如果发现有两个苹果一样重的怎么办?
哪个苹果应该排在前面呢?
你可能需要在提供一个Comparator进来进一步定义这一个比较。
比如,在按重量比较两个苹果之后,你可能想要按颜色排序。thenComparing方法就是用来做来这个的。它接收一个函数作为参数(就像comparing方法一样)。如果两个对象用第一个Comparator比较之后是一样的,就提供第二个Comparator。
你又可以优雅的解决这个问题:
inventory.sort(Comparator.comparing(Apple::getWeight)
.reversed() //按照重量递减排序
.thenComparing(Apple::getColor)); //两个苹果一样重时,进一步按颜色排序
谓词复合
谓词接口包括三个方法:negate、and和or,让你可以重用已有的Predicate来创建更加复杂的谓词。
negate
比如你已使用negate方法来返回一个Predicate的非,比如苹果不是红的:
Predicate<Apple> redApple = a->a.getWeight().equals("red");
Predicate<Apple> notRedApple = redApple.negate();
and
你可能想要把两个Lambda用and方法组合起来,比如一个苹果既是红色又比较重:
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
or
你可以进一步组合谓词,表达要么是重(150g以上)的红苹果,要么是绿苹果:
Predicate<Apple> redAndHeavyApple =
redApple.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor()));//链接Predicate的方法来构造更加复杂Predicate对象
这一点为什么很好呢?
从简单Lambda表达式出发,你可以构建更加复杂的表达式,但是读起来仍然和问题的陈述差不多!
请注意,and和or是按照在表达式链中的位置,从左往右确定优先级的。
因此,a.or(b).and(c)可以看做(a | b) && c。 |
函数复合
最后,你可以把Function接口代表的Lambda表达式复合起来。
Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
andThen
andThen方法会返回一个函数,它对输入应用到一个给定函数,再对输出应用到另一个函数。
比如,假设有一个函数f数字加1(x -> x + 1),另一个函数给g给数字乘以2,你可以将它们组合成一个函数h,先给数字加1,再给结果乘以2。
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); //数学上写作g(f(x))
int result = h.apply(1); //返回的是4
compose
你可以类似的使用compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。
比如上一个例子用compose的话,它将意味着f(g(x)),而andThen则意味着g(f(x)):
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g); //数学上写作f(g(x))
int result = h.apply(1); //返回的是3
实际使用
这一切听起来有点太抽象了。那么在实际中这有什么用呢?
比方说,你有一系列工具方法,对用String表示的一封信做文本转换:
public class Letter {
public static String addHeader(String text){
return "From pby" + text;
}
public static String addFooter(String text){
return text + "Kind regards";
}
public static String checkSpelling(String text){
return text.replaceAll("labda", "lambda");
}
}
现在你可以通过复合这些工具方法来创建各种转型流水线了,比如创建一个流水线:先加上头部,然后进行拼写检查,最后加上一个落款。
Function<String ,String> addHeader = Letter::addHeader;
Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling)
.andThen(Letter::addFooter);
第二个流水线可能只加头部和落款,而不做拼写检查:
Function<String ,String> addHeader = Letter::addHeader;
Function<String ,String> transformationPipeline = addHeader.andThen(Letter::addFooter);
参考资料
《Java 8 实战》