介绍
什么是函数式编程
函数式编程是一种编程范式,即一切都是数学函数。在Java面向对象编程中,程序是一系列相互作用(方法)的对象,而在函数式编程中,程序会是一个无状态的函数组合序列。
概念看不懂很正常,我们来看一个例子:
1、定义一个“函数接口”
/**
* 自定义`函数接口`
*
* @author: mock
* @date: 2023-01-21 14:09:48
*/
@FunctionalInterface
public interface DogFunction {
void doAction(String action);
}
2、使用接口函数
/**
* 使用接口函数
*
* @author: mock
* @date: 2023-01-21 14:17:17
*/
public class DogFunctionDemo {
public static void main(String[] args) {
//函数Lambda表达式,从这里就可以看到函数编程的特点
DogFunction eat = action -> {
System.out.println("执行动作" + action);
System.out.println("吃东西");
};
DogFunction run = action -> {
System.out.println("执行动作" + action);
System.out.println("跑步");
};
doAction(eat,"1");
doAction(run,"2");
}
/**
* `DogFunction` 将函数接口作为参数传入
*/
public static void doAction(DogFunction action, String step) {
action.doAction(step);
}
}
用法
@FunctionalInterface自定义函数接口注解
Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
示例
// 注解定义一个函数接口
@FunctionalInterface
public interface Person {
void sayHello(String msg);
}
// 使用Lambda表达式定义函数,将函数直接作为变量输出
Person personSay = message -> System.out.println("Hello " + message);
personSay.sayHello("`mock`");
Function 函数接口
JDK1.8新特性,传入一个参数,返回一个值的函数。例如stream.map()
方法。
大家可以看下源码:
@FunctionalInterface
public interface Function<T, R> {
//接收一个参数,返回一个值
R apply(T t);
//函数链式执行
//接收一个函数,执行函数,并且把该函数返回值作为参数执行`apply`,最终返回一个函数
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
//函数链式执行 和`compose差不多`
//区别是先执行了函数的`apply`方法后再执行参数`apply`
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;
}
}
单看方法解释和源码,是不是一头雾水?那我们看看下面的例子就能懂啦
/**
* 使用`Function`实现类
*
* @author: Mock
* @date: 2022-05-25 22:47:28
*/
public class FunctionDemo {
public static void main(String[] args) {
//Function 可作为一个方法参数
FunctionTest functionTest = new FunctionTest();
//1.执行函数方法 `apply`
functionMethod1(value -> value + 1);
//2.执行函数方法 `compose`
functionMethod2(
value -> {
System.out.println("执行opt1");
return value + 1;
},
value -> {
System.out.println("执行opt2");
return value + 2;
}
);
//3.执行函数方法 `compose`
functionMethod3(
value -> {
System.out.println("执行opt1");
return value + 1;
},
value -> {
System.out.println("执行opt2");
return value + 2;
}
);
}
/**
* 执行函数方法`apply`
*
* @param opt 函数
*/
private static void functionMethod1(Function<Integer, Integer> opt) {
System.out.println("开始执行:functionMethod1");
System.out.println("functionMethod1 执行结果: " + opt.apply(1));
System.out.println();
}
/**
* 执行函数方法`compose`
*
* @param opt1 函数1
* @param opt2 函数2
*/
private static void functionMethod2(
Function<Integer, Integer> opt1,
Function<Integer, Integer> opt2
) {
System.out.println("开始执行:functionMethod2");
System.out.println("functionMethod2 执行结果: " + opt1.compose(opt2).apply(3));
System.out.println();
}
/**
* 执行函数方法`andThen`
*
* @param opt1 函数1
* @param opt2 函数2
*/
private static void functionMethod3(
Function<Integer, Integer> opt1,
Function<Integer, Integer> opt2
) {
System.out.println("开始执行:functionMethod2");
System.out.println("functionMethod3 执行结果: " + opt1.andThen(opt2).apply(3));
System.out.println();
}
}
输出结果
可以看到 :functionMethod1
执行apply
,接收一个参数,执行并且返回functionMethod2
执行compose
,先执行opt2
再执行opt1
functionMethod3
执行andThen
,先执行opt1
再执行opt2
BiFunction函数接口
JDK1.8新特性,传入两个参数(第一个参数可以是上个参数返回值),返回一个返回值
大家接着看一下源码
@FunctionalInterface
public interface BiFunction<T, U, R> {
//接收两个参数,返回一个值
R apply(T t, U u);
//函数链,根函数执行结果做为after的参数
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
那么大家可能会问了,为什么BiFunction里面没有compose方法。
根据Function接口里面的compose方法,我们可以分析出,它是先执行参数的apply方法,在执行调用者的apply方法。假如BiFunction里面有compose,又因为BiFunction接受两个参数,那么通过上面的分析,可以得出,它的compose方法的参数必定是BiFunction类型的,但是下一步执行调用者的apply方法的时候,因为只有一个参数,所以是无法满足的。所以BiFunction里面没有compose方法。
其他一些函数接口
BiFunction<T,U,R>:代表了一个接受两个输入参数的方法,并且返回一个结果
DoubleFunction<R>:代表接受一个double值参数的方法,并且返回结果
DoubleToIntFunction:接受一个double类型输入,返回一个int类型结果。
DoubleToLongFunction:接受一个double类型输入,返回一个long类型结果
IntFunction<R>:接受一个int类型输入参数,返回一个结果 。
IntToDoubleFunction:接受一个int类型输入,返回一个double类型结果 。
IntToLongFunction:接受一个int类型输入,返回一个long类型结果。
LongFunction<R>: 接受一个long类型输入参数,返回一个结果。
LongToDoubleFunction: 接受一个long类型输入,返回一个double类型结果。
LongToIntFunction:接受一个long类型输入,返回一个int类型结果。
ToDoubleBiFunction<T,U>:接受两个输入参数,返回一个double类型结果
ToDoubleFunction<T>:接受一个输入参数,返回一个double类型结果
ToIntBiFunction<T,U>:接受两个输入参数,返回一个int类型结果。
ToIntFunction<T>:接受一个输入参数,返回一个int类型结果。
ToLongBiFunction<T,U>:接受两个输入参数,返回一个long类型结果。
ToLongFunction<T>:接受一个输入参数,返回一个long类型结果。
小牛试刀一下
上面说了那么多,给大伙看一个计算器
的实现示例,看看函数式编程的应用
/**
* 计算器枚举
*
* @author: Mock
* @date: 2023-05-28 15:27:49
*/
@Getter
@AllArgsConstructor
public enum Calculator {
/**
* 加法
*/
ADDITION((v1, v2) -> v1 + v2),
/**
* 减法
*/
SUBTRACTION((v1, v2) -> v1 - v2),
/**
* 乘法
*/
MULTIPLICATION((v1, v2) -> v1 * v2),
/**
* 除法
*/
DIVISION((v1, v2) -> v1 / v2);
/**
* 计算器函数方法
*/
private BiFunction<Double, Double, Double> computer;
public static void main(String[] args) {
Double v1 = 15D;
Double v2 = 3D;
System.out.println("执行加法 v1 + v2 = :" + Calculator.ADDITION.getComputer().apply(v1,v2));
System.out.println("执行减法 v1 - v2 = :" + Calculator.SUBTRACTION.getComputer().apply(v1,v2));
System.out.println("执行乘法 v1 * v2 = :" + Calculator.MULTIPLICATION.getComputer().apply(v1,v2));
System.out.println("执行除法 v1 / v2 = :" + Calculator.DIVISION.getComputer().apply(v1,v2));
}
}
执行结果
从例子中可以看到,我们很方便的在枚举中,将函数做为成员属性定义给枚举,减少了很多代码,且一目了然。
在很多编码场景都可以利用函数这种方式去实现。