优雅编程之函数式接口

JAVA乐园

共 11938字,需浏览 24分钟

 ·

2021-05-05 11:19

函数式接口 (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 对象,返回 booleanboolean 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<StringString> function = s -> String.format("%s, Jack!", s);
    Function<StringString> compose = s -> StringUtils.isEmpty(s) ? "Hello" : s;
    Function<StringString> 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

喜欢,在看


浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报