优雅编程之函数式接口
共 12160字,需浏览 25分钟
·
2021-05-09 13:39
点击上方“程序员大白”,选择“星标”公众号
重磅干货,第一时间送达
函数式接口 (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 错误。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
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 类型的输入参数,不返回执行结果
@Test
public void testConsumer() {
// Consumer<T>:accept(T t)
Consumer<String> consumer = System.out::println;
consumer.accept("hello world!"); // hello world!
// Consumer<T>:andThen(Consumer<? super T> after) -> 返回一个组合函数,after将会在该函数执行之后应用
StringBuilder sb = new StringBuilder("Hello ");
Consumer<StringBuilder> consumer_accept = (str) -> str.append("Jack! ");
Consumer<StringBuilder> consumer_andThen = (str) -> str.append("Bob!");
consumer_accept.andThen(consumer_andThen).accept(sb);
System.out.println(sb.toString()); // Hello Jack! Bob!
}
BiConsumer<T, U> :提供两个自定义类型的输入参数,不返回执行结果
@Test
public void testBiConsumer() {
// BiConsumer<T, U>:accept(T t, U u)
BiConsumer<String, String> biConsumer = (a, b) -> System.out.printf("%s %s!", a, b);
biConsumer.accept("hello", "world"); // hello world!
}
DoubleConsumer :提供一个 double 类型的输入参数,不返回执行结果
@Test
public void testDoubleConsumer() {
// DoubleConsumer:accept(double value)
DoubleConsumer doubleConsumer = System.out::println;
doubleConsumer.accept(9.12D); // 9.12
}
ObjDoubleConsumer<T> :提供一个 double 类型的输入参数和一个 T 类型的输入参数,不返回执行结果
@Test
public void testObjDoubleConsumer() {
// ObjDoubleConsumer<T>:accept(T t, double value)
ObjDoubleConsumer<String> stringObjDoubleConsumer = (s, value) -> System.out.println(s + value);
stringObjDoubleConsumer.accept("金额:", 9.12D); // 金额:9.12
}
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 类型的结果
@Test
public void testPredicate() {
// Predicate<T>:boolean test(T t)
Predicate<List<String>> listPredicate = Collection::isEmpty;
System.out.println(listPredicate.test(Arrays.asList("Hello", "World"))); // false
// Predicate<T>:boolean test(T t)
Predicate<Integer> predicate = integer -> integer != 0;
System.out.println(predicate.test(10)); // true
// Predicate<T>:Predicate<T> and(Predicate<? super T> other)
predicate = predicate.and(integer -> integer >= 10);
System.out.println(predicate.test(10)); // true
// Predicate<T>:Predicate<T> or(Predicate<? super T> other)
predicate = predicate.or(integer -> integer != 10);
System.out.println(predicate.test(10)); // true
// Predicate<T>:Predicate<T> negate()
predicate = predicate.negate();
System.out.println(predicate.test(10)); // false
}
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 类型的结果
@Test
public void testFunction() {
// Function<T, R>:R apply(T t)
Function<String[], List<String>> asList = Arrays::asList;
System.out.println(asList.apply(new String[]{"Hello", "World"})); // [Hello, World]
Function<String, String> function = s -> String.format("%s, Jack!", s);
Function<String, String> compose = s -> StringUtils.isEmpty(s) ? "Hello" : s;
Function<String, String> andThen = String::toUpperCase;
String s = function.compose(compose).andThen(andThen).apply("");
System.out.println(s); // HELLO, JACK!
}
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 类型的结果
@Test
public void testSupplier() {
// Supplier<T>:T get();
Supplier<String> supplier = () -> "Hello Jack!";
System.out.println(supplier.get()); // Hello Jack!
}
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 类型的结果
@Test
public void testOperator() {
// UnaryOperator<T>:T apply(T t)
UnaryOperator<String> unaryOperator = String::toUpperCase;
System.out.println(unaryOperator.apply("Hello World!")); // HELLO WORLD!
}
BinaryOperator<T> :提供两个 T 类型的输入参数,返回一个 T 类型的结果
@Test
public void testBinaryOperator() {
// BinaryOperator<T>:T apply(T t1, T T2);
BinaryOperator<BigInteger> binaryOperator = BigInteger::add;
System.out.println(binaryOperator.apply(BigInteger.ONE, BigInteger.TEN));
}
总结
java.util.function 包已经为大家提供了大量标注的函数接口。只要标准的函数接口能够满足需求,通常应该优先考虑,而不是专门再构建一个新的函数接口。这样会使 API 更加容易学习,通过减少它的概念内容,显著提升互操作性优势,因为许多标准的函数接口都提供了有用的默认方法。
参考博文
[1]. JDK8 新特性 - java.util.function-Function 接口
[2]. JAVA8 的 java.util.function 包
source: https://morning-pro.github.io/archives/43810ae.html
推荐阅读
关于程序员大白
程序员大白是一群哈工大,东北大学,西湖大学和上海交通大学的硕士博士运营维护的号,大家乐于分享高质量文章,喜欢总结知识,欢迎关注[程序员大白],大家一起学习进步!