java8 内部提供的函数式接口

2021/09/03 JAVA8 共 8360 字,约 24 分钟
闷骚的程序员

1. java8内置的函数式接口

jdk8中,在java.util.function包中提供了大量的函数式接口,这些接口都是函数式接口,且都使用 @FunctionalInterface 标注。
java8中内置的函数式接口分类:
(1)消费型接口 Consumer
(2)供给型接口 Supplier
(3)函数型接口 Function
(4)断言型接口 Predicate

消费型接口 Consumer.
给定T类型的参数,进行逻辑处理,不返回任何值,直接消费。

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

供给型接口 Supplier.
不输入任何值,直接得到一个T类型的输出。

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {

    T get();
}

函数型接口 Function.
给定T类型的参数,返回R类型的结果。
主要用来对输入进行逻辑处理,然后进行输出。

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

断言型接口 Predicate.
给定T类型的参数,计算出断言式的boolean结果。
主要用来对输入做判断,输出判断结果。

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

下面我们逐个分析。

2. 消费型接口 Consumer

2.1 Consumer接口

(1)源码:

package java.util.function;

import java.util.Objects;

// 函数式接口
// 表示“接受一个参数输入且不需要有任何返回值的操作。”
// 不同于其他的函数式接口,Consumer期望通过方法的实现来执行一段不需要返回值的逻辑。
@FunctionalInterface
public interface Consumer<T> {

    // 抽象方法,接口一个T类型参数且没有返回值
    void accept(T t);

    // 默认方法,提供链式调用方式执行。
    // 执行流程:先执行本身的accept方法,再执行传入参数after的accept方法。
    // 该方法会抛出NullPointException异常。
    // 如果在执行调用链时出现异常,会将异常传递给调用者,且发生异常后的after将不会再调用。
    // 实际上,这个andThen方法就干了一件事:返回了一个新的Consumer对象。
    // 这个新的Consumer对象复写的accept方法的逻辑分两步:先执行当前调用者的accept方法,再执行传入的after的accept方法。
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

(2)案例:

package zeh.myjavase.code42java8.demo05;

import java.util.function.Consumer;

/**
 * 功能描述:消费型函数式接口 Consumer
 *
 * @ClassName ConsumerRun
 * @Author zhaoerhu
 * @Date 2023/3/21 21:06
 */
public class ConsumerRun {

    public static void main(String[] args) {
        testConsumer();
        testAndThen();
    }

    // 测试Consumer接口,将一个数字+1后输出
    public static void testConsumer() {
        // lambda表达式创建Consumer对象
        Consumer<Integer> addOne = num -> System.out.println(num + 1);
        addOne.accept(20);
    }

    // Consumer接口还提供了一个andThen的默认方法。
    // 该方法表示,先执行当前Consumer对象的accept方法,再执行传入参数after的accept方法。
    // 并且执行完毕返回一个Consumer对象以支持链式编程。
    public static void testAndThen() {
        // 使用方法引用输出参数
        Consumer<String> consumer1 = name -> System.out.println("consumer1:" + name);

        Consumer<String> consumer2 = name -> {
            System.out.println("consumer2:" + name);
            // 手动抛出一个异常
            throw new NullPointerException();
        };

        Consumer<String> consumer3 = name -> System.out.println("consumer3:" + name);

        // consumer1.andThen(consumer2):
        // 返回一个新的Consumer对象,该对象实现的accept方法逻辑是:先执行consumer1实现的accept方法,再执行consumer2的accept方法。
        // consumer1.andThen(consumer2).andThen(consumer3):
        // 然后链式起来,实际上返回了一个全新的Consumer对象,该对象实现的accept方法的逻辑是,先执行consumer1的accept,再执行consumer2的accept,最后执行consumer3的accept方法。
        // 这里的andThen要好好接一下,它的本质并不是在触发accept方法的执行,而是返回一个新的Consumer对象。
        // 也就是说,通过链式的调用andThen方法,只不过是在创建一个新的Consumer对象。
        // 只不过andThen方法返回的Consumer对象,它实现的accept方法逻辑,实际上是将多个consumer对象的accept方法调用连接起来了而已。
        // 它的本质还是一段逻辑体,是一个Lambda表达式,是一个函数式接口的对象。
        // 无论多少个consumer对象通过andThen方法组合起来,它最终只会返回一个新的Consumer对象,这个新的Consumer对象代表着accept方法的具体执行逻辑:
        // 那就是,按照链式传入的顺序来顺序化执行多个consumer对象的accept方法,先执行第一个consumer的accept,再执行第二个consumer,以此类推。
        // 此处的3个consumer对象最终组合了一个新的consumer对象,但是它被触发时接收的实际参数其实只能是一个,就是下面传入的eric。
        // 因此,无论多少个Consumer对象通过andThen进行组合,每一个Consumer对象实现的accept方法接收的参数类型得统一。
        Consumer<String> complexConsumer = consumer1.andThen(consumer2).andThen(consumer3);

        // 在此处传入实际数据,触发accept方法的执行。
        // 所以说,complexConsumer对象实际上只是实现了此处要执行的accept方法的执行逻辑而已。
        // 真正的触发执行还需要调用accept方法。
        // 从输出结果看:andThen返回的链式节点中,只要有一个consumer的accept执行抛出了异常,则后续的consumer对象将不再执行。
        complexConsumer.accept("eric");

    }
}

输出:

21
consumer1:eric
consumer2:eric
Exception in thread "main" java.lang.NullPointerException
	at zeh.myjavase.code42java8.demo05.ConsumerRun.lambda$testAndThen$2(ConsumerRun.java:36)
	at java.util.function.Consumer.lambda$andThen$0(Consumer.java:65)
	at java.util.function.Consumer.lambda$andThen$0(Consumer.java:65)
	at zeh.myjavase.code42java8.demo05.ConsumerRun.testAndThen(ConsumerRun.java:51)
	at zeh.myjavase.code42java8.demo05.ConsumerRun.main(ConsumerRun.java:16)

Process finished with exit code 1

2.2 BiConsumer接口

(1)源码:

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface BiConsumer<T, U> {

    // 与Consumer接口相比,BiConsumer接口可以同时接收两个参数进行处理
    void accept(T t, U u);


    // andThen用于链式组合多个BiConsumer对象,生成一个新的BiConsumer
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);

        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}

(2)案例:

package zeh.myjavase.code42java8.demo05;

import java.util.function.BiConsumer;

// BiConsumer接口,可以接收2个参数的Consumer消费型接口
public class BiConsumerRun {

    public static void main(String[] args) {
        testBiConsumer();
        testAndThen();
    }


    public static void testBiConsumer() {
        BiConsumer<String, Integer> biConsumer = (name, num) -> System.out.println(name + "在放学路上捡到了" + num + "毛钱!");
        biConsumer.accept("eric", 5);
    }


    public static void testAndThen() {
        // 无论多少个BiConsumer对象通过andThen在组合逻辑产生一个新的BiConsumer,它最终只能接收一份实际入参。
        // 因此,每个biConsumer对象实现的accept方法的逻辑都要符合这一份实际入参的语义。

        // 下面分别是三个biConsumer对象实现的语义,尽管展示了不同的逻辑,但操作加工的实际参数,其实都是"李宁"和10
        // 李宁昨天花了10块钱请我吃饭
        // 李宁为了10块钱把我门牙打掉了
        // 一件李宁衣服卖10块钱
        BiConsumer<String, Integer> biConsumer1 = (name, num) -> System.out.println(name + "昨天花了" + num + "块钱请我吃饭");
        BiConsumer<String, Integer> biConsumer2 = (name, num) -> System.out.println(name + "为了" + num + "块钱把我门牙打掉了");
        BiConsumer<String, Integer> biConsumer3 = (a, b) -> System.out.println("一件" + a + "衣服卖" + b + "块钱");
        biConsumer1.andThen(biConsumer2).andThen(biConsumer3).accept("李宁", 10);
    }
}

输出:

eric在放学路上捡到了5毛钱!
李宁昨天花了10块钱请我吃饭
李宁为了10块钱把我门牙打掉了
一件李宁衣服卖10块钱

Process finished with exit code 0

2.3 IntConsumer接口

(1)源码:

package java.util.function;

import java.util.Objects;

// 很简单了,是Consumer的一个特殊定制化,只接受一个int参数
@FunctionalInterface
public interface IntConsumer {

    void accept(int value);

    default IntConsumer andThen(IntConsumer after) {
        Objects.requireNonNull(after);
        return (int t) -> { accept(t); after.accept(t); };
    }
}

(2)案例:

package zeh.myjavase.code42java8.demo05;

import java.util.function.IntConsumer;

// 只接受一个int参数的Consumer接口
public class IntConsumerRun {

    public static void main(String[] args) {
        testIntConsumer();
        testAndThen();
    }

    public static void testIntConsumer() {
        IntConsumer intConsumer = num -> System.out.println("我的尺寸只有" + num + "公分");
        intConsumer.accept(10);
    }

    public static void testAndThen() {
        IntConsumer intConsumer1 = num -> System.out.println("他的身高是" + num + "公分");
        IntConsumer intConsumer2 = num -> System.out.println("这件外套" + num + "块钱");
        IntConsumer intConsumer3 = num -> System.out.println("警报响了" + num + "次");
        intConsumer1.andThen(intConsumer2).andThen(intConsumer3).accept(176);
    }
}

运行:

我的尺寸只有10公分
他的身高是176公分
这件外套176块钱
警报响了176次

Process finished with exit code 0

类似的,单参数Consumer还有 DoubleConsumer 和 LongConsumer ,分别接受一个double参数和一个long参数。

2.4 ObjIntConsumer接口

(1)源码:

package java.util.function;

// 接受1个T类型的参数对象和一个int参数
@FunctionalInterface
public interface ObjIntConsumer<T> {

    // 和BiConsumer相比,该接口显式接受一个对象参数,和一个int参数
    // 从接口名称就能看出来,ObjIntConsumer,指的是一个Object和一个Int
    // 与BiConsumer不同的是,一个对象和一个int参数,不允许链式组合使用andThen。
    // 其实从使用场景来理解,同时传递一个对象和一个int参数,一般不会用于链式组合场景。
    // 如果想接受两个参数用于andThen链式组合,那么请直接使用BiConsumer接口即可、
    void accept(T t, int value);
}

(2)案例:

package zeh.myjavase.code42java8.demo05;

import java.util.function.ObjIntConsumer;

// 接受2个参数,第1个参数是对象,第2个参数是int的Consumer
public class ObjIntConsumerRun {

    public static void main(String[] args) {
        testObjIntConsumer();
    }

    // ObjIntConsumer 接口不支持andThen操作
    public static void testObjIntConsumer() {
        ObjIntConsumer<String> objIntConsumer = (name, num) -> System.out.println(name + "花了" + num + "块钱就追到了女朋友");
        objIntConsumer.accept("张三", 100);
    }
}

运行:

张三花了100块钱就追到了女朋友

Process finished with exit code 0

类似的,双参数Consumer还有 ObjDoubleConsumer 和 ObjLongConsumer ,ObjDoubleConsumer接受一个对象和一个double参数,ObjLongConsumer接受一个对象和一个long对象。

文档信息

Search

    Table of Contents