Java函数式接口再理解
Java函数式接口再理解
前言
这篇文章主要是我个人对函数式接口的再理解,若有理解偏差或考虑不周的地方,欢迎批评指出,共同进步!
这里做下记录,以便日后遗忘查阅;
目录
一、什么是函数式接口
① 概述
我们都知道接口的作用是定义一组抽象规范,供实现类进行实际实现。只要类实现了该接口,都要实现其中的抽象方法。
在Java8之后接口中不仅可以定义抽象方法,还可以定义非抽象方法。
因此,只存在一个抽象方法的接口,我们称之为函数式接口。
在JDK中提供的函数式接口都加上了@FunctionalInterface
注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
注意:如果一个接口本身不存在抽象方法,其继承了父接口是一个函数式接口,其也是一个函数式接口。
② 一个标准的函数式接口
③ 举例感受函数式接口的作用
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. 标准函数式接口
2. 非标志函数式接口
可以看到,当不满足函数式接口规则时,添加该注解会有报错提示。
二、常见函数式接口及常见说法说明
① 消费型函数式接口
1. 什么是消费型函数式接口
所谓消费型函数式接口,其实就是只进不出型。例如如下JDK1.8
提供的Consumer
函数式接口,只接受任意类型的入参,但没有返回值。这种消费数据的接口,一般称为消费型函数式接口。
2.举例:Consumer
② 计算转换型函数式接口
1. 什么是计算转换型函数式接口
所谓计算转换型函数式接口,其实就是有进有出型。例如如下JDK1.8
提供的Function
函数式接口,接受任意类型的入参,返回任意类型的出参。这种转换数据的接口,一般称为计算转换型函数式接口。
2.举例:Function
③ 判断型函数式接口
1. 什么是判断型函数式接口
所谓判断型函数式接口,其实就是有入参其返回boolean型。例如如下JDK1.8
提供的Predicate
函数式接口,接受任意类型的入参,返回boolean类型出参。这种断言数据的接口,一般称为判断型函数式接口。
2.举例:Predicate
④ 生产型函数式接口
1. 什么是生产型函数式接口
所谓生产型函数式接口,其实就是无入有出型。例如如下JDK1.8
提供的Supplier
函数式接口,不接受入参,返回任意类型的出参。这种生产数据的接口,一般称为生产型函数式接口。
2.举例:Supplier
三、通过函数式接口常用默认方法学习植入链式编程思想
从上述学习中,我们了解到函数式接口的一些基本作用和用法。我们也知道JDK1.8之后接口中也可以定义默认方法,部分函数式接口会定义一些默认方法供使用。我们接下来来学习该如何使用这些默认方法。
接下来我将使用判断型函数式接口Predicate
来进行学习举例。
① 引入stream.filter
我们知道,Java-stream
流中实现的filter
方法需要一个Predicate
函数式接口的实现类来指定过滤数据条件。所以在使用时我们需要创建匿名实现类,来实现该函数式接口方法。
② 使用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
类型。
于是我们就可以通过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
表达式写法的同学,可以去尝试简化一下上述代码案例。
其实实际上你会发现,调用函数式接口默认方法的时候,只能使用匿名实现类来进行操作,所以一般我们不这样去进行实际开发,只有你自己想要去自定义一些函数式编程的一些方法时才回去使用。
我这里举例的重点是理解一下链式编程思想。