Java 闭包复合

Java 闭包复合,用户希望连续应用一系列简单且独立的函数,使用 FunctionConsumerPredicate 接口中定义为默认方法的复合方法(composition method)。函数式编程的优点之一在于支持创建若干简单、可重复使用的函数,将这些函数组合在一起就能解决复杂的问题。

Java 闭包复合 问题描述

用户希望连续应用一系列简单且独立的函数。

Java 闭包复合 解决方案

使用 FunctionConsumerPredicate 接口中定义为默认方法的复合方法(composition method)。

Java 闭包复合 具体实例

函数式编程的优点之一在于支持创建若干简单、可重复使用的函数,将这些函数组合在一起就能解决复杂的问题。为此,java.util.function 包引入的函数式接口定义了各种有助于简化复合操作的方法。
例如,Function 接口包括 composeandThen 两种默认方法,二者的签名如例 5-23 所示。

例 5-23 Function 接口定义的复合方法

default <V> Function<V,R>       compose(Function<? super V,? extends T> before)
default <V> Function<T,V>       andThen(Function<? super R,? extends V> after)

从方法签名中的哑元(dummy argument)不难看出两种方法的作用:compose 方法在原始函数之前应用其参数,而 andThen 方法在原始函数之后应用其参数。
为说明两种方法的应用,考虑例 5-24 所示的简单示例。

例 5-24 composeandThen 方法的应用

Function<Integer, Integer> add2  = x -> x + 2;
Function<Integer, Integer> mult3 = x -> x * 3;

Function<Integer, Integer> mult3add2  = add2.compose(mult3); ➊
Function<Integer, Integer> add2mmult3 = add2.andThen(mult3); ➋

System.out.println("mult3add2(1): " + mult3add2.apply(1));
System.out.println("add2mult3(1): " + add2mult3.apply(1));

❶ 先执行 mult3 函数,再执行 add2 函数
❷ 先执行 add2 函数,再执行 mult3 函数
可以看到,add2 函数的作用是将参数加 2,而 mult3 函数的作用是将参数乘 3。通过 compose 方法创建的复合函数 mult3add2 先执行 mult3,再执行 add2;通过 andThen 方法创建的复合函数 add2mult3 则相反,先执行 add2,再执行 mult3
两个复合函数的执行结果如下:

mult3add2(1): 5 // 因为(1 * 3) + 2 == 5
add2mult3(1): 9 // 因为(1 + 2) * 3 == 9

复合函数的结果仍然是函数,因此这个过程创建的操作可供今后使用。例如,如果收到的数据属于 HTTP 请求的一部分,说明数据是以字符串形式传输的。虽然已有可用于操作数据的方法,但前提为数据是数字。如果这种情况频繁发生,可以考虑在应用数值操作之前编写一个解析字符串数据的函数。详见例 5-25。

例 5-25 首先将字符串解析为整数,然后加 2

Function<Integer, Integer> add2 = x -> x + 2;
Function<String, Integer> parseThenAdd2 = add2.compose(Integer::parseInt);
System.out.println(parseThenAdd2.apply("1"));
// 打印3

本例创建了一个名为 parseThenAdd2 的函数,它先调用静态方法 Integer.parseInt,再将所得结果加 2。另一方面,也可以定义一个先执行数值操作,再调用 toString 方法的函数,如例 5-26 所示。

例 5-26 首先加 2,然后将数字转换为字符串

Function<Integer, Integer> add2 = x -> x + 2;
Function<Integer, String> plus2toString = add2.andThen(Object::toString);
System.out.println(plus2toString.apply(1));
// 打印"3"

上述操作返回一个函数,它传入 Integer 参数并返回 String
如例 5-27 所示,Consumer 接口也定义了一个用于闭包复合的方法。

例 5-27 Consumer 接口定义的复合方法

default Consumer<T> andThen(Consumer<? super T> after)

根据 Javadoc 的描述,andThen 方法返回一个复合 Consumer,它依次执行原始操作和 after 指定的操作。如果执行任一操作时抛出异常,异常将被转发给组合操作的调用者。
示例代码如例 5-28 所示。

例 5-28 用于打印和记录的复合 Consumer

Logger log = Logger.getLogger(...);
Consumer<String> printer = System.out::println;
Consumer<String> logger = log::info;

Consumer<String> printThenLog = printer.andThen(logger);
Stream.of("this", "is", "a", "stream", "of", "strings").forEach(printThenLog);

程序首先创建了两个 Consumer,一个用于打印结果到控制台,另一个用于日志记录。接下来,程序创建了一个复合 Consumer,可以一次性打印并记录流的所有元素。
Predicate 接口定义了三种用于谓词复合的方法,如例 5-29 所示。

例 5-29 Predicate 接口定义的复合方法

default Predicate<T>      and(Predicate<? super T> other)
default Predicate<T>      negate()
default Predicate<T>      or(Predicate<? super T> other)

andornegate 方法分别使用逻辑与、逻辑或、逻辑非操作实现谓词的复合,每种方法返回一个复合 Predicate
接下来,我们讨论一个关于整数的有趣问题。完全平方数(perfect square)是其平方根同样为整数的数,而三角形数(triangle number)是一定数目的点或圆在等距离排列下可以形成等边三角形的数8
8参见维基百科有关“三角形数”的介绍。在一个房间中,如果每个人只与其他人握手一次,则三角形数是握手次数的总和。(例如,房间中有 2 个人时握手次数为 1,有 3 个人时握手次数为 3,有 4 个人时握手次数为 6,有 5 个人时握手次数为 10,有 6 个人时握手次数为 15,以此类推。那么 1、3、6、10、15 就是前 5 个三角形数,第 n 个三角形数的计算公式为

例 5-30 创建了两个用于计算完全平方数和三角形数的方法,并通过 and 方法查找既是完全平方数,又是三角形数的数。

例 5-30 既是完全平方数,又是三角形数的数

public static boolean isPerfect(int x) {    ➊
    return Math.sqrt(x) % 1 == 0;
}

public static boolean isTriangular(int x) {    ➋
    double val = (Math.sqrt(8 * x + 1) - 1) / 2;
    return val % 1 == 0;
}

// 其他代码

IntPredicate triangular = CompositionDemo::isTriangular;
IntPredicate perfect = CompositionDemo::isPerfect;
IntPredicate both = triangular.and(perfect);

IntStream.rangeClosed(1, 10_000)
        .filter(both)
        .forEach(System.out::println);       ➌

❶ 部分完全平方数:1、4、9、16、25、36、49、64、81……
❷ 部分三角形数:1、3、6、10、15、21、28、36、45……
❸ 既是完全平方数,又是三角形数(1 到 10 000 之间):1、36、1225
借由复合函数,可以将若干简单的函数组合在一起以构建复杂的操作 9
9Unix 操作系统就是基于这种理念构建的,具有类似的优点。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程