前言
函数式接口 (Functional Interface) 就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。Java 允许利用 Lambda 表达式创建这些接口的实例。java.util.function 包是 Java 8 增加的一个新技术点 “函数式接口”,此包共有 43 个接口。别指望能够全部记住他们,但是如果能记住其中 6 个基础接口,必要时就可以推断出其余接口了。这些接口是为了使 Lamdba 函数表达式使用的更加简便,当然你也可以自己自定义接口来应用于 Lambda 函数表达式。
JDK 1.8 API 包含了很多内建的函数式接口,比如 Comparator 或者 Runnable 接口,这些接口都增加了 @FunctionalInterface 注解以便能用在 Lamdba 上。现如今,我们则从 Function 常用函数入口,真正了解一下函数式接口。
Java 8 中函数式接口
接口 | 描述 | 函数签名 | 范例 |
---|---|---|---|
UnaryOperator<T> | 接收 T 对象,返回 T 对象 | T apply(T t) | String::toLowerCase |
BinaryOprator<T> | 接收两个 T 对象,返回 T 对象 | T apply(T t1, T t2) | BigInteger::add |
Predicate<T> | 接收 T 对象,返回 boolean | boolean test(T t) | Collection::isEmpty |
Function<T, R> | 接收 T 对象,返回 R 对象 | R apply(T t) | Arrays::asList |
Supplier<T> | 提供 T 对象(例如工厂),不接收值 | T get() | Instant::new |
Consumer<T> | 接收 T 对象,不返回值 | void accept(T t) | System.out::println |
标注为 @FunctionalInterface 的接口被称为函数式接口,该接口有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。是否是一个函数式接口,需要注意的有以下几点:
- 该注解只能标记在“有且仅有一个抽象方法”的接口上。
- Java 8 接口中的静态方法和默认方法,都不算是抽象方法。
- 接口默认继承 java.lang.Object,所以如果接口显示声明覆盖了 Object 中方法,那么也不算抽象方法。
- 该注解不是必须的,如果一个接口符合 “函数式接口” 定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了 @FunctionInterface,那么编译器会报错。
- 在一个接口中定义两个自定义的方法,就会产生 Invalid ‘@FunctionalInterface’ annotation; FunctionalInterfaceTest is not a functional interface 错误。
1 |
|
Consumer:消费型接口(void accept(T t))
函数式接口 | 描述 |
---|---|
Consumer<T> | 提供一个 T 类型的输入参数,不返回执行结果 |
BiConsumer<T, U> | 提供两个自定义类型的输入参数,不返回执行结果 |
DoubleConsumer | 提供一个 double 类型的输入参数,不返回执行结果 |
IntConsumer | 提供一个 int 类型的输入参数,不返回执行结果 |
LongConsumer | 提供一个 long 类型的输入参数,不返回执行结果 |
ObjDoubleConsumer<T> | 提供一个 double 类型的输入参数和一个 T 类型的输入参数,不返回执行结果 |
ObjIntConsumer<T> | 提供一个 int 类型的输入参数和一个 T 类型的输入参数,不返回执行结果 |
ObjLongConsumer<T> | 提供一个 long 类型的输入参数和一个 T 类型的输入参数,不返回执行结果 |
(1)作用:消费某个对象
(2)使用场景:Iterable 接口的 forEach 方法需要传入 Consumer,大部分集合类都实现了该接口,用于返回 Iterator 对象进行迭代。
(3)主要方法
方法 | 描述 |
---|---|
void accept(T t) | 对给定的参数执行操作 |
default Consumer<T> andThen(Consumer< ? super T> after) | 返回一个组合函数,after 将会在该函数执行之后应用 |
(4)代码示例
Consumer<T>:提供一个 T 类型的输入参数,不返回执行结果
1 | @Test |
BiConsumer<T, U> :提供两个自定义类型的输入参数,不返回执行结果
1 |
|
DoubleConsumer :提供一个 double 类型的输入参数,不返回执行结果
1 |
|
ObjDoubleConsumer<T> : 提供一个 double 类型的输入参数和一个 T 类型的输入参数,不返回执行结果
1 | @Test |
Predicate:断言型接口(boolean test(T t))
函数式接口 | 描述 |
---|---|
Predicate<T> | 提供一个 T 类型的输入参数,返回一个 boolean 类型的结果 |
BiPredicate<T,U> | 提供两个自定义类型的输入参数,返回一个 boolean 类型的结果 |
DoublePredicate | 提供一个 double 类型的输入参数,返回一个 boolean 类型的结果 |
IntPredicate | 提供一个 int 类型的输入参数,返回一个 boolean 类型的结果 |
LongPredicate | 提供一个 long 类型的输入参数,返回一个 boolean 类型的结果 |
(1)作用:判断对象是否符合某个条件
(2)使用场景:ArrayList 的 removeIf(Predicate):删除符合条件的元素。如果条件硬编码在 ArrayList 中,它将提供无数的实现,但是如果让调用者传入条件,这样 ArrayList 就可以从复杂和无法猜测的业务中解放出来。
(3)主要方法
方法 | 描述 |
---|---|
boolean test(T t) | 根据给定的参数进行判断 |
Predicate<T> and(Predicate< ? super T> other) | 返回一个组合判断,将 other 以短路并且的方式加入到函数的判断中 |
Predicate<T> or(Predicate< ? super T> other) | 返回一个组合判断,将 other 以短路或的方式加入到函数的判断中 |
Predicate<T> negate() | 将函数的判断取反 |
(4)代码示例
Predicate<T> : 提供一个 T 类型的输入参数,返回一个 boolean 类型的结果
1 | @Test |
Function:函数型接口(R apply(T t))
函数式接口 | 描述 |
---|---|
Function<T, R> | 提供一个 T 类型的输入参数,返回一个 R 类型的结果 |
BiFunction<T, U, R> | 提供两个自定义类型的输入参数,返回一个 R 类型的结果 |
DoubleFunction<R> | 提供一个 double 类型的输入参数,返回一个 R 类型的结果 |
DoubleToIntFunction | 提供一个 double 类型的输入参数,返回一个 int 类型的结果 |
DoubleToLongFunction | 提供一个 double 类型的输入参数,返回一个 long 类型的结果 |
IntFunction<R> | 提供一个 int 类型的输入参数,返回一个 R 类型的结果 |
IntToDoubleFunction | 提供一个 int 类型的输入参数,返回一个 double 类型的结果 |
IntToLongFunction | 提供一个 int 类型的输入参数,返回一个 long 类型的结果 |
LongFunction<R> | 提供一个 long 类型的输入参数,返回一个 R 类型的结果 |
LongToDoubleFunction | 提供一个 long 类型的输入参数,返回一个 double 类型的结果 |
LongToIntFunction | 提供一个 long 类型的输入参数,返回一个 int 类型的结果 |
ToDoubleBiFunction<T, U> | 提供两个自定义类型的输入参数,返回一个 double 类型的结果 |
ToDoubleFunction<T> | 提供一个 T 类型的输入参数,返回一个 double 类型的结果 |
ToIntBiFunction<T, U> | 提供两个自定义类型的输入参数,返回一个 int 类型的结果 |
ToIntFunction<T> | 提供一个 T 类型的输入参数,返回一个 int 类型的结果 |
ToLongBiFunction<T, U> | 提供两个自定义类型的输入参数,返回一个 long 类型的结果 |
ToLongFunction<T> | 提供一个 T 类型的输入参数,返回一个 long 类型的结果 |
(1)作用:实现一个”一元函数“,即传入一个值经过函数的计算返回另一个值。
(2)使用场景:V HashMap.computeIfAbsent(K , Function<K, V>):如果指定的 key 不存在或相关的 value 为 null 时,设置 key 与关联一个计算出的非 null 值,计算出的值为 null 的话什么也不做(不会去删除相应的 key)。如果 key 存在并且对应 value 不为 null 的话什么也不做。
(3)主要方法
方法 | 描述 |
---|---|
R apply(T t) | 将此参数应用到函数中 |
Function<T, V> andThen(Function< ? super R, ? extends V> after) | 返回一个组合函数,该函数结果应用到 after 函数中 |
Function<V, R> compose(Function< ? super V, ? extends T> before) | 返回一个组合函数,首先将入参应用到 before 函数,再将 before 函数结果应用到该函数中 |
(4)代码示例
Function<T, R> : 提供一个 T 类型的输入参数,返回一个 R 类型的结果
1 |
|
Supplier:供给型接口(R apply(T t))
函数式接口 | 描述 |
---|---|
Supplier<T> | 不提供输入参数,返回一个 T 类型的结果 |
BooleanSupplier | 不提供输入参数,返回一个 boolean 类型的结果 |
DoubleSupplier | 不提供输入参数,返回一个 double 类型的结果 |
IntSupplier | 不提供输入参数,返回一个 int 类型的结果 |
LongSupplier | 不提供输入参数,返回一个 long 类型的结果 |
(1)作用:创建一个对象(工厂类)
(2)使用场景:Optional.orElseGet(Supplier< ? extends T>):当 this 对象为 null,就通过传入 supplier 创建一个 T 返回。
(3)主要方法
方法 | 描述 |
---|---|
T get() | 获取结果值 |
(4)代码示例
Supplier<T> : 不提供输入参数,返回一个 T 类型的结果
1 | @Test |
Operator:操作型接口(T apply(T t))
函数式接口 | 描述 |
---|---|
UnaryOperator<T> | 提供一个 T 类型的输入参数,返回一个 T 类型的结果 |
BinaryOperator<T> | 提供两个 T 类型的输入参数,返回一个 T 类型的结果 |
DoubleBinaryOperator | 提供两个 double 类型的输入参数,返回两个 double 类型的结果 |
DoubleUnaryOperator | 提供一个 double 类型的输入参数,返回一个 double 类型的结果 |
IntBinaryOperator | 提供两个 int 类型的输入参数,返回一个 int 类型的结果 |
IntUnaryOperator | 提供一个 int 类型的输入参数,返回一个 int 类型的结果 |
LongBinaryOperator | 提供两个 long 类型的输入参数,返回一个 long 类型的结果 |
LongUnaryOperator | 提供一个 long 类型的输入参数,返回一个 long 类型的结果 |
(1)作用:实现一个”一元函数“,即传入一个值经过函数的计算返回另一个同类型的值。
(2)使用场景:UnaryOperator 继承了 Function,与 Function 作用相同,不过 UnaryOperator,限定了传入类型和返回类型必需相同。
(3)主要方法
方法 | 描述 |
---|---|
T apply(T t) | 将给定参数应用到函数中 |
Function<T, V> andThen(Function< ? super T, ? extends V> after) | 返回一个组合函数,该函数结果应用到 after 函数中 |
Function<V, T> compose(Function< ? super V, ? extends T> before) | 返回一个组合函数,首先将入参应用到 before 函数,再将 before 函数结果应用到该函数中 |
(4)代码示例
UnaryOperator<T> : 提供一个 T 类型的输入参数,返回一个 T 类型的结果
1 | @Test |
BinaryOperator<T> :提供两个 T 类型的输入参数,返回一个 T 类型的结果
1 | @Test |
总结
java.util.function 包已经为大家提供了大量标注的函数接口。只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门再构建一个新的函数接口。这样会使 API 更加容易学习,通过减少它的概念内容,显著提升互操作性优势,因为许多标准的函数接口都提供了有用的默认方法。
参考博文
[1]. JDK8 新特性 - java.util.function-Function 接口
[2]. JAVA8 的 java.util.function 包
Java8 那些事儿系列
- Java8 那些事儿(一):Stream 函数式编程
- Java8 那些事儿(二):Optional 类解决空指针异常
- Java8 那些事儿(三):Date/Time API(JSR 310)
- Java8 那些事儿(四):增强的 Map 集合
- Java8 那些事儿(五):函数式接口
- Java8 那些事儿(六):从 CompletableFuture 到异步编程