1. 什么是函数式编程
- 英文名Functional Programming
- 从java8开始,和lambda表达式一起结合使用
- 一般使用FunctionInterface注解来标注一个函数接口
- 通过复合没有副作用的纯函数,来编写业务逻辑代码
- 是一个无状态的函数,执行结果不依赖外部变量值,即同样的入参列表,总是得到相同的结果
- 函数作为一段功能代码,可以像变量一样进行引用和传递,以便在有需要的时候进行调用
2. 为什么要用函数式编程
- 在代码的书写、视觉感观上更加简洁优雅一点
- 可以满足动态、灵活的业务逻辑实现
- 函数之间通过链式调用连接在一起,完成一些小而简的业务逻辑编写
- 发挥函数式编程在并行计算上的优势
3. 怎么用函数式编程
1. 简单使用
现有函数接口定义都在java.util.function这个包下面,先找一个现有的jdk中自带的一个函数接口IntFunction来看看
@FunctionalInterface
public interface IntFunction<R> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
R apply(int value);
}
可以看到,它仍然还是一个接口,具有接口的基本特征,那就好办了,我们还写一个简单的调用实现:
//函数接口的业务逻辑定义
IntFunction<Integer> intFunc = new IntFunction<Integer>() {
@Override
public Integer apply(int value) {
return value*4;
}
};
//函数接口的实现调用
int result = intFunc.apply(2);
System.out.printf("2*4 = %d;", result);
控制台输出:
//控制台输出
2*4 = 8;
这里是对接口里的抽象方法作了实现,再通过手动调用,传进入参,执行了上面的实现代码,得到了最终的结果
相信此时,你可能会惊呼,现在已经不需要这么写了,对的,我们有更简洁的书写形式,如下:
//函数接口的业务逻辑定义
// IntFunction<Integer> intFunc = new IntFunction<Integer>() {
// @Override
// public Integer apply(int value) {
// return value*4;
// }
// };
IntFunction<Integer> intFunc = i->i*4;
其实,它是这样一步步进化而来的:
2. 现有基础函数接口汇总
java8类库已经为我们预定义一些函数接口,方便我们来使用,也可以认为是一些函数模板,下面简要说明一下它们的功能
序号 | 接口 | 说明 | 其它 |
---|---|---|---|
1 | BiConsumer<T,U> | 接收两个入参的操作,不返回结果 | andThen方法支持连接操作 |
2 | BiFunction<T,U,R> | 接收两个入参的操作,返回结果 | andThen方法支持连接操作 |
3 | BinaryOperator<T> | 继承BiFunction,入参和出参类型相同 | maxB最大返回,minBy最小返回 |
4 | BiPredicate<T,U> | 两个入参,返回布尔值 | and方法支持与操作 negate方法支持取反操作 or方法支持或操作 |
5 | BooleanSupplier | 无参,返回布尔值 | |
6 | Consumer<T> | 一个入参,不返回结果 | andThen方法支持连接操作 |
7 | DoubleBinaryOperator | 两个double入参,返回double结果 | |
8 | DoubleConsumer | 一个double入参,不返回结果 | andThen支持连接操作 |
9 | DoubleFunction<R> | 一个double入参,并返回结果 | |
10 | DoublePredicate | 一个double入参,并返回布尔值 | and方法支持与操作 negate方法支持取反操作 or方法支持或操作 |
11 | DoubleSupplier | 无参,返回一个double结果 | |
12 | DoubleToIntFunction | 入参double类型,返回int类型 | |
13 | DoubleToLongFunction | 入参double类型,返回long类型 | |
14 | DoubleUnaryOperator | 入参double类型,返回double类型 | andThen方法支持连接操作 compose方法支持组合操作 identity方法返回入参 |
15 | Function<T,R> | 一个入参,一个结果 | andThen方法支持连接操作 compose方法支持组合操作 identity方法返回入参 |
16 | IntBinaryOperator | 两个int入参,返回int结果 | |
17 | IntConsumer | 一个int入参,不返回结果 | andThen支持连接操作 |
18 | IntFunction<R> | 一个int入参,并返回结果 | |
19 | IntPredicate | 一个int入参,并返回布尔值 | and方法支持与操作 negate方法支持取反操作 or方法支持或操作 |
20 | IntSupplier | 无参,返回一个int结果 | |
21 | IntToDoubleFunction | 入参int,返回double类型 | |
22 | IntToLongFunction | 入参int,返回long类型 | |
23 | IntUnaryOperator | 入参int类型,返回int类型 | andThen方法支持连接操作 compose方法支持组合操作 identity方法返回入参 |
24 | LongBinaryOperator | 入参两个long类型,返回long类型 | |
25 | LongConsumer | 入参long类型,无返回值 | andThen支持连接操作 |
26 | LongFunction<R> | 一个long入参,并返回结果 | |
27 | LongPredicate | 一个long入参,并返回布尔值 | and方法支持与操作 negate方法支持取反操作 or方法支持或操作 |
28 | LongSupplier | 无参,返回一个long结果 | |
29 | LongToDoubleFunction | 入参long类型,返回double类型 | |
30 | LongToIntFunction | 入参long类型,返回int类型 | |
31 | LongUnaryOperator | 入参long类型,返回long类型 | andThen方法支持连接操作 compose方法支持组合操作 identity方法返回入参 |
32 | ObjDoubleConsumer<T> | 入参对象和double类型,无返回值 | |
33 | ObjIntConsumer<T> | 入参对象和int类型,无返回值 | |
34 | ObjLongConsumer<T> | 入参对象和long类型,无返回值 | |
35 | Predicate<T> | 一个入参,返回布尔值 | and方法支持与操作 negate方法支持取反操作 or方法支持或操作 |
36 | Supplier<T> | 无参,返回一个结果 | |
37 | ToDoubleBiFunction<T,U> | 两个入参,返回double类型 | |
38 | ToDoubleFunction<T> | 一个入参,返回double类型 | |
39 | ToIntBiFunction<T,U> | 两个入参,返回int类型 | |
40 | ToIntFunction<T> | 一个入参,返回int类型 | |
41 | ToLongBiFunction<T,U> | 两个入参,返回long类型 | |
42 | ToLongFunction<T> | 一个入参,返回long类型 | |
43 | UnaryOperator<T> | 一个入参,返回相同类型的结果 |
下面对里面最常用的andThen方法做一个简单示例讲解
- andThen方法
Consumer c = o->{System.out.println("c-" + o);};
Consumer c1 = o->{System.out.println("c1-" + o);};
Consumer c2 = o->{System.out.println("c2-" + o);};
//通过andThen方法,可以将多个函数接口进行连接处理
c.andThen(c2).andThen(c1).accept("hello world!");
最终的输出的结果:
c-hello world!
c2-hello world!
c1-hello world!
我们会发现c本身的输出逻辑先执行,再依次执行后面的c2,c1的输出过程
我们可以通过源码,看到基本的执行过程:
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
//accept(t); 先执行自身的处理逻辑
//after.accept(t); 再执行入参的逻辑
return (T t) -> { accept(t); after.accept(t); };
}
其它的方法,实现过程也基本类似
最后,可以看一下,在我们使用的较多的Stream接口中,存在着大量的函数式接口的参数类型
对函数式编程的相关概念和API有了一定的了解后,对这些API的学习也会有较大的帮助。