函数式编程当然已经非常普遍了,对于Java而言,异步编程和流式编程都是常见的应用场景,lambda表达式没少用。今天我要分享的是基于函数式接口自定义业务模板的实践。
我们首先回顾下函数式编程
函数式编程是一种编程范式。它把计算当成是数学函数的求值,从而避免改变状态和使用可变数据。它是一种声明式的编程范式,通过表达式和声明而不是语句来编程。
函数式编程接口
接口名称 | 特征 | 用途 | 方法名 |
---|---|---|---|
Supplier<T> |
无参数,返回一个结果 | 获取值或实例,工厂模式,延迟计算 | T get() |
Comparator<T> |
接受两个参数,返回 int 值 |
比较两个对象,用于排序和比较操作 | int compare(T o1, T o2) |
Consumer<T> |
接受一个参数,无返回值 | 执行操作,通常是副作用操作 | void accept(T t) |
BiConsumer<T, U> |
接受两个参数,无返回值 | 执行操作,通常是副作用操作 | void accept(T t, U u) |
Function<T, R> |
接受一个参数,返回一个结果 | 将输入转换为输出,如数据转换或映射 | R apply(T t) |
BiFunction<T, U, R> |
接受两个参数,返回一个结果 | 将两个输入转换为输出 | R apply(T t, U u) |
Predicate<T> |
接受一个参数,返回 boolean 值 |
测试参数是否满足特定条件 | boolean test(T t) |
BiPredicate<T, U> |
接受两个参数,返回 boolean 值 |
测试两个参数是否满足特定条件 | boolean test(T t, U u) |
UnaryOperator<T> |
接受一个参数,返回相同类型的结果 | 对输入执行单一操作并返回相同类型 | T apply(T t) |
BinaryOperator<T> |
接受两个相同类型参数,返回相同类型结果 | 将两个值组合成一个新值 | T apply(T t1, T t2) |
除此之外,JDK还提供了一些针对基本类型的接口,比如IntConsumer.accept(int value),目测是因为基础类型没法抽象成类T
多参数函数式接口
java.util.functional 中的接口是有限的,如果需要 3 个参数函数的接口怎么办?自己创建就可以了,如下:
// 创建处理 3 个参数的函数式接口
@FunctionalInterface
public interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
高阶函数(Higher-order Function)
在函数式编程中非常常见,它有以下特点:
接收一个或多个函数作为参数,返回一个函数作为结果
函数组合(Function Composition)
意为 “多个函数组合成新函数”。它通常是函数式 编程的基本组成部分。在 java.util.function 中常用的支持函数组合的方法,大致如下:
函数式接口 | 方法名 | 描述 |
---|---|---|
Function<T, R> |
andThen |
用于从左到右组合两个函数,即:h(x) = g(f(x)) |
Function<T, R> |
compose |
用于从右到左组合两个函数,即:h(x) = f(g(x)) |
Consumer<T> |
andThen |
用于从左到右组合两个消费者,按顺序执行两个消费者操作 |
Predicate<T> |
and |
用于组合两个谓词函数,返回一个新的谓词函数,满足两个条件 |
Predicate<T> |
or |
用于组合两个谓词函数,返回一个新的谓词函数,满足任一条件 |
Predicate<T> |
negate |
用于对谓词函数取反,返回一个新的谓词函数,满足相反的条件 |
UnaryOperator<T> |
andThen |
用于从左到右组合两个一元操作符,即:h(x) = g(f(x)) |
UnaryOperator<T> |
compose |
用于从右到左组合两个一元操作符,即:h(x) = f(g(x)) |
BinaryOperator<T> |
andThen |
用于从左到右组合两个二元操作符,即:h(x, y) = g(f(x, y)) |
知道有这么个东西,想用的时候甚至想问下能不能用的时候找GPT就好了
业务重构实践
事出起因是我们从下游拿到的数据格式非常原始,基本都是String,一些Object类型的字段其接口规格没有严格限制,我们需要校验并转型成内部可用的类。
特别地对于枚举类、布尔值、甚至基于业务条件有校验前更新需求的字段,校验的过程中就已经完成了转型,可以直接赋值到内部模型。
将校验和映射强行拆成两个独立的大步骤显然有重复工作,于是我发现了这个难的的应用函数式接口的机会,将单个字段的处理封装成一个统一的模版validateConvertAndMap,当然为了减少应用模版的代码量,还有其他重载的模版这里就不列举了
可以看到validateConvertAndMap把待校验字段的提取、检验及可选转型、映射这三个步骤用函数式接口抽象了出来,对于单个字段的处理整体实现了一步到位、不会有重复的工作。
public class Processor {
public static <T, R> void validateConvertAndMap(
String fieldName,
Supplier<T> fieldSupplier,
Function<T, R> validator, // with optional conversion
Consumer<R> mapper,
ProcessorContext context
) {
T fieldValue = fieldSupplier.get();
try {
R validatedValue = (validator != null) ? validator.apply(fieldValue) : (R) fieldValue;
if (mapper != null) {
mapper.accept(validatedValue);
}
} catch (MyException e) {
// wrap exception in context
}
}
public static <E extends Enum<E>> Function<String, E> enumCastValidator(Class<E> klass, E defaultValue) {
return (e) -> {
if (StringUtils.isEmpty(e)) {
return defaultValue;
}
for (final E enumConstant : klass.getEnumConstants()) {
if (enumConstant.name().equals(e)) {
return enumConstant;
}
}
throw new MyException("xxx")
}
}
}
特别地,对于validator函数还有高阶函数的玩法,比如enum的校验转型validator写成了二阶函数,通过传入enum类型和默认值返回一个针对特定enum类型的校验函数,进一步优化了代码重用
Reference: https://www.cnblogs.com/xiao2shiqi/p/17280618.html
评论区