函数接口
在函数式编程中,纯函数的定义是:
- 此函数在相同的输入值时,需产生相同的输出。函数的输出和输入值以外的其他隐藏信息或状态无关,也和由 I/O 设备产生的外部输出无关。
- 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
为使 Java 支持函数式编程,函数接口(Functional Interfaces)正是 Java 8 引入的一个核心概念。函数接口要求接口中只能定义个唯一一个抽象方法。同时,引入一个新的注解: @FunctionalInterface
,作为函数接口的标记。
Java 8 提供了四大类函数接口:谓词 Predicate
,函数 Function
,提供者 Supplier
,消费者 Consumer
。以及衍生的函数接口,比如 BiFunction<T, U, R>
支持两个入参的函数等等,它们都定义在 java.util.function
包下。
Lambda 表达式
函数式接口的重要属性是:我们能够使用 Lambda 实例化它们。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码。
Lambda 表达式由三个部分组成:第一部分为一个括号内用逗号分隔的函数入参;第二部分为箭头符号 ->
;第三部分为方法体,可以是表达式和代码块。语法如下:
(parameters) ‑> expression
方法体为表达式,该表达式的值作为返回值返回。(parameters) ‑> { statements; }
方法体为代码块,必须用{}
包裹起来,若该函数需要返回值则需要return
。
用 Lambda 表达式替换匿名内部类的写法如下。再进一步,可以使用方法引用(Method Reference)简写
1 | // Anonymous inner class |
使用 parallelStream()
和 Lambda 表达式并行操作集合,默认线程数为 CPU 核数
1 | urls.parallelStream() |
谓词 Predicate
在计算机语言的环境下,谓词是指条件表达式的求值返回真或假的过程。在 Java 中就是进行逻辑判断后返回值为 boolean
的函数,对应函数接口为 Predicate<T>
。在针对集合进行筛选时,通常会使用谓词对集合元素进行条件判断。
1 |
|
Predicate<T>
接口定义的唯一抽象方法 test(T t)
用以验证输入参数是否满足谓词条件。同时,它还提供了默认方法用以支持组合的与、或、非逻辑运算。
1 | Predicate<Apple> p1 = p ‑> p.getColor() == Color.RED; |
函数 Function
Function<T, R>
接口代表一个函数,准确地说,代表了具有输入输出的函数,这也是最常见的函数定义。具有两个入参的函数对应接口为 BiFunction<T, U, R>
。
1 | /** |
函数也可以进行组合,组合后返回的结果也是一个函数,这就是所谓的组合子。组合子方法分别为:
compose(before)
,组合前一个函数,相当于V -> T -> R
andThen(after)
,组合后一个函数,相当于T -> R -> V
提供者 Supplier
当一个函数仅仅用于返回结果时,我们可以将这种函数称为提供者,接口定义为 Supplier<T>
。
1 |
|
只提供一个 get()
方法,我们可以将这种提供者代表的函数看做是一种 Query 操作。
消费者 Consumer
与提供者对应的接口则被称之为消费者,用以接收一个输入,但却并不关心返回结果,即返回值为 void
。消费者的对应接口为 Consumer<T>
。
1 | /** |
不像其他函数接口,Consumer
通常是有副作用的。这也不难理解,该函数只有输入没有输出,那数据通常是被外部系统消费了。同时 Consumer
支持 andThen(after)
组合
1 | Consumer<String> c1 = s -> System.out.println(s); |
这种消费者代表的函数相当于是一个 Command 操作。软件设计的一条原则就是 CQS(Command-Query Separation)命令-查询分离原则,即保证一个方法体内只有 Query 和 Command 的一种,就是因为查询无副作用而命令可能会改变数据状态。