Java函数式接口再理解

Java函数式接口再理解


前言

这篇文章主要是我个人对函数式接口的再理解,若有理解偏差或考虑不周的地方,欢迎批评指出,共同进步!

这里做下记录,以便日后遗忘查阅;


目录


一、什么是函数式接口

① 概述

我们都知道接口的作用是定义一组抽象规范,供实现类进行实际实现。只要类实现了该接口,都要实现其中的抽象方法。

在Java8之后接口中不仅可以定义抽象方法,还可以定义非抽象方法。

因此,只存在一个抽象方法的接口,我们称之为函数式接口。

在JDK中提供的函数式接口都加上了@FunctionalInterface注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。

注意:如果一个接口本身不存在抽象方法,其继承了父接口是一个函数式接口,其也是一个函数式接口。

② 一个标准的函数式接口

image-20231130141137487

③ 举例感受函数式接口的作用

import java.util.function.IntBinaryOperator;

public class Main {
    public static void main(String[] args) {
        // 实际逻辑为加操作时,如下实现
        var jiaResult = test(new IntBinaryOperator() {
            @Override
            public int applyAsInt(int left, int right) {
                return left + right;
            }
        });
        System.out.println("jiaResult:" + jiaResult);

        // 实际逻辑为减操作时,如下实现
        var jianResult = test(new IntBinaryOperator() {
            @Override
            public int applyAsInt(int left, int right) {
                return left - right;
            }
        });
        System.out.println("jianResult:" + jianResult);
        // ...或者任何其他操作。
    }

    public static int test(IntBinaryOperator operator) {
        /*
         * 该方法在此之前的逻辑操作
         * */
        // 假设不管通过什么手段我们最终得到了两个int类型的值
        int a = 1;
        int b = 2;
        // 中间部分逻辑我们还不确定或者逻辑是动态的,但我们知道不管中间逻辑怎么动态的变化,我们最终要的是一个int类型的数据。
        // 那么我们就可以在函数式接口里面去找一个,入参是两个int类型,出参是一个int类型的函数式接口,来进行抽象处理。
        // 在具体调用的时候再去写这部分的逻辑实现。是加-减-乘-除或其他都根据业务需要进行代码实现。
        int result = operator.applyAsInt(a, b);
        /*
         * 得到这个int类型的数据之后做一堆后续操作
         * */
        return result;
    }
}

④ 控制台打印结果

jiaResult:3
jianResult:-1

⑤ 补充:@FunctionalInterface注解作用

其实在上面我们已经知道了JDK自定义的函数式接口都会加上这个注解。如果不加,但是接口中只有一个抽象方法,其也是一个函数式接口。其实@FunctionalInterface注解就是为了验证该接口是不是一个函数式接口,这一点可以类比@Override注解理解。

下面我们自定义一个函数试接口来演示一下@FunctionalInterface注解的作用:

1. 标准函数式接口

image-20231130151253976

2. 非标志函数式接口

image-20231130151419135

可以看到,当不满足函数式接口规则时,添加该注解会有报错提示。

二、常见函数式接口及常见说法说明

① 消费型函数式接口

1. 什么是消费型函数式接口

所谓消费型函数式接口,其实就是只进不出型。例如如下JDK1.8提供的Consumer函数式接口,只接受任意类型的入参,但没有返回值。这种消费数据的接口,一般称为消费型函数式接口。

2.举例:Consumer

image-20231130152825015

② 计算转换型函数式接口

1. 什么是计算转换型函数式接口

所谓计算转换型函数式接口,其实就是有进有出型。例如如下JDK1.8提供的Function函数式接口,接受任意类型的入参,返回任意类型的出参。这种转换数据的接口,一般称为计算转换型函数式接口。

2.举例:Function

image-20231130153610536

③ 判断型函数式接口

1. 什么是判断型函数式接口

所谓判断型函数式接口,其实就是有入参其返回boolean型。例如如下JDK1.8提供的Predicate函数式接口,接受任意类型的入参,返回boolean类型出参。这种断言数据的接口,一般称为判断型函数式接口。

2.举例:Predicate

image-20231130154047453

④ 生产型函数式接口

1. 什么是生产型函数式接口

所谓生产型函数式接口,其实就是无入有出型。例如如下JDK1.8提供的Supplier函数式接口,不接受入参,返回任意类型的出参。这种生产数据的接口,一般称为生产型函数式接口。

2.举例:Supplier

image-20231130154832639

三、通过函数式接口常用默认方法学习植入链式编程思想

从上述学习中,我们了解到函数式接口的一些基本作用和用法。我们也知道JDK1.8之后接口中也可以定义默认方法,部分函数式接口会定义一些默认方法供使用。我们接下来来学习该如何使用这些默认方法。

接下来我将使用判断型函数式接口Predicate来进行学习举例。

① 引入stream.filter

我们知道,Java-stream流中实现的filter方法需要一个Predicate函数式接口的实现类来指定过滤数据条件。所以在使用时我们需要创建匿名实现类,来实现该函数式接口方法。

image-20231130192636669

② 使用stream.filter

创建匿名实现类,实现函数式接口方法。

    public static void main(String[] args) {
        var result = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).filter(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer > 2;
            }
        }).toList();
        System.out.println(result);
    }

运行结果:

[3, 4, 5, 6, 7, 8, 9]

可以看出我们进行了一次数据过滤。

③ 使用Predicate的默认方法and二次过滤数据

根据如下默认and方法我们可以看到,其入参和返回值都是Predicate类型。

image-20231130193649574

于是我们就可以通过Predicate匿名实现类来调用默认and方法,再次传入Predicate匿名实现类,因为默认and方法的返回值仍是Predicate的匿名实现类,所以我们仍然可以继续链式调用Predicate匿名实现类中的默认方法。通过这个例子我想你应该可以对链式编程有一个初步的了解。

代码如下:

    public static void main(String[] args) {
        var result = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).filter(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer > 2;
            }
        }.and(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer < 7;
            }
        })).toList();
        System.out.println(result);
    }

运行结果:

[3, 4, 5, 6]

其实到这里,如果学过lambda表达式写法的同学,可以去尝试简化一下上述代码案例。

其实实际上你会发现,调用函数式接口默认方法的时候,只能使用匿名实现类来进行操作,所以一般我们不这样去进行实际开发,只有你自己想要去自定义一些函数式编程的一些方法时才回去使用。

我这里举例的重点是理解一下链式编程思想。

posted @ 2023-11-30 20:22  zhao-XH  阅读(68)  评论(0编辑  收藏  举报