Function函数介绍
我们在没深入了解Function函数式相关接口之前,可能只是在使用Stream流处理时,用过它的相关接口。有些同学也就止步于此,并没有深入了解过它的设计理念。
Function中文接口文档
Stream接口文档
对于Stream流大家常用的方法有哪些?
- filter(Predicate<? super T> predicate)// 条件筛选
- map(Function<? super T, ? extends R> mapper)// 对单个item对象转换操作
- flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)// 对item对象操作,并返回一个新的Stream流
- forEach(Consumer<? super T> action)// 循环遍历
- anyMatch(Predicate<? super T> predicate)// 条件筛选,整个集合item有一个为true
- allMatch(Predicate<? super T> predicate)// 条件筛选,全部item为true
- reduce(BinaryOperator accumulator);// 累计计算,值累加等
- collect(Collector<? super T, A, R> collector);// 集合操作,元素汇总转换计算
- 看着这些常用方法,入参都是Function相关接口函数
Function常用函数详解
Predicate函数
Predicate函数
传入一个参数,返回一个boolean值的函数,例如filter方法。
// strList是一个List. filter方法需要传入Predicate函数方法.
// 获取集合数量,并且集合不存在null字符串
long count1 = strList.stream().filter(item -> StringUtils.hasLength(item))
.count();
// 简写
long count2 = strList.stream().filter(StringUtils::hasLength).count();
// 自定义方法,理论上我们只要是一个 传入一个String类型参数并返回一个boolean参数的方法。都可以放入filter方法
long count3 = strList.stream().filter(item -> customHasLength(item)).count()
// 简写
long count4 = strList.stream().filter(FunctionDemoApplication::customHasLength).count();
/**
- 自定义方法,字符串不为null或空字符串
*/
public static boolean customHasLength(String str){
return StringUtils.hasLength(str);
}
Function函数
传入一个参数,返回一个值的函数。例如map方法。
public static void main(String[] args) {
List planList = new ArrayList<>(Collections.emptyList());
planList.add(new Plan(1L,"SUCCEED"));
planList.add(new Plan(2L,"FAIL"));
// map方法入参,需要传入一个Function函数。item -> item.getPlanNo())写法属于
// Function函数规范。item表示入参,item.getPlanNo()表示返参
Set<Long> planNoList1 = planList.stream().map(item -> item.getPlanNo()).collect(Collectors.toSet());
// 简写
Set<Long> planNoList2 = planList.stream().map(Plan::getPlanNo).collect(Collectors.toSet());
}
static class Plan{
private Long planNo;
private String planStatus;
public Plan(Long planNo, String planStatus){
this.planNo = planNo;
this.planStatus = planStatus;
}
public Long getPlanNo() {
return planNo;
}
public Plan setPlanNo(Long planNo) {
this.planNo = planNo;
return this;
}
public String getPlanStatus() {
return planStatus;
}
public Plan setPlanStatus(String planStatus) {
this.planStatus = planStatus;
return this;
}
}
Consumer函数
这是一个传入一个参数,无返回值的函数。例如forEach方法。
public static void main(String[] args) {
List planList = new ArrayList<>();
planList.add(new Plan(1L,"SUCCEED"));
planList.add(new Plan(2L,"FAIL"));
planList.add(new Plan(3L,"SUCCEED"));
List<Long> planNo = new ArrayList<>();
planList.stream()
.filter(plan -> "SUCCEED".equals(plan.getPlanStatus()))
// forEarch方法入参,需要传入一个Consumer函数。plan -> planNo.add(plan.getPlanNo())符合函数规范。 plan表示入参。
.forEach(plan -> planNo.add(plan.getPlanNo()));
System.out.println("count:" + planNo.size());
}
BiFunction函数
与Function区别在于,BiFunction允许传入2个参数,返回一个值。
BiConsumer函数
与Consumer函数区别在于,BiConsumer函数允许传入2个参数,无返回值。
BinaryOperator函数
这是BiFunction的一个子接口,BiFunction可以传入2个参数,返回一个值。BinaryOperator则是传入的2个参数和返回的值类型必须一致,所以用在reduce函数方法入参时,就可以用来进行值累加的操作。例如传入BigDecimal::add,Long::sum,甚至于BigDecimal::max,用来获取最大值。
总结一句话,Function函数作为方法入参,核心原因就是把具体业务逻辑交由外层处理。使方法只处理核心逻辑,从而减少业务逻辑对方法的侵入性,使代码更加简洁优雅,易于维护。
例如map(Function<? super T, ? extends R> mapper)方法,就是把集合中单个item对象的处理逻辑交由外部逻辑处理。 map方法内只需获取结果进行核心逻辑处理。
再例如filter(Predicate<? super T> predicate)方法,把具体校验逻辑交由外部处理,filter方法内只需获取结果进行核心逻辑处理。
实际场景Function函数应用
当我了解Stream对Function的函数运用后,我佩服这些源码编写大佬的思路与才华。所以我认为核心是需要学习他们的编码设计思路,在日常编写代码中,真的碰到了类似场景,就可以想起并运用这套思想去设计代码,使得我们代码更加优雅,简洁高效。这也是我们学习源码的初衷。
例如有这样一个场景,我们对接了很多支付渠道,每家对接口参数的加密方式不同。我们可以针对支付渠道设计一个抽象类。在抽象类中定义一个签名方法。具体不同渠道的签名规则以Function函数传入。
抽象类中公共签名方法
/**
- 排序并签名
* - @param params 要签名参数
- @param append 待签名字符串追加内容
- @param signName 签名参数
- @param signer 签名函数
@return 最终签名
*/
protected final String sign(Map params,String append, String signName, Function<String, String> signer) {
String signStr = params.keySet().stream()
.filter(key -> StringUtils.hasLength(key) && !key.equals(signName) && StringUtils.hasLength(params.get(key))) .sorted() .map(key -> key + "=" + params.get(key)) .collect(Collectors.joining("&"));
signStr += append;
if (logger.isDebugEnabled()) logger.debug("待签名字符串:{}", signStr);
return signer.apply(signStr);
}
/**
- md5 大写加密
*/
protected final String md5Upper(String signStr) {
return md5Origin(signStr).toUpperCase();
}
/**
- md5 加密
*/
protected final String md5Origin(String signStr) {
return DigestUtils.md5Hex(signStr);
}
具体支付渠道进行签名调用
// 调用抽象类中的sign签名方法
String sign = sign(params, "&key=" + "私钥字符串", "sign"
// 这里不同渠道类中也可自定义加密规则
, this::md5Origin);
// 另外一种写法
String sign2 = sign(params, "&key=" + "私钥字符串", "sign"
, (signStr) -> DigestUtils.md5Hex(signStr));
类似的场景其实有很多,在很多源码使用上,都会把业务逻辑通过函数式入参,交由你来处理。使得源码方法的公用性会更强,代码更加简洁易于维护。
以上便是我对于Function函数入门的介绍,如果要更加深入的了解与运用,需要对源码贡献者的编程思路达成共鸣,才能一劳永逸。最后愿你我的头发茂盛。
End.