深度探秘 Java 8 函数式编程(下)
Posted 芋道源码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度探秘 Java 8 函数式编程(下)相关的知识,希望对你有一定的参考价值。
技术文章第一时间送达!
源码精品专栏
69 篇
61 篇
来源:http://t.cn/ELmra8O
函数式编程的益处
Java8泛型
完整代码示例
小结
函数式编程的益处
更精练的代码
函数编程的一大益处,是用更精练的代码表达常用数据处理模式。函数接口能够轻易地实现模板方法模式,只要将不确定的业务逻辑抽象成函数接口,然后传入不同的lambda表达式即可。博文“精练代码:一次Java函数式编程的重构之旅” 展示了如何使用函数式编程来重构常见代码,萃取更多可复用的代码模式。
这里给出一个列表分组的例子。实际应用常常需要将一个列表 List[T] 转换为一个 Map[K, List[T]] , 其中 K 是通过某个函数来实现的。 看下面一段代码:
public static Map<String, List<OneRecord>> buildRecordMap(List<OneRecord> records, List<String> colKeys) {
Map<String, List<OneRecord>> recordMap = new HashMap<>();
records.forEach(
record -> {
String recordKey = buildRecordKey(record.getFieldValues(), colKeys);
if (recordMap.get(recordKey) == null) {
recordMap.put(recordKey, new ArrayList<OneRecord>());
}
recordMap.get(recordKey).add(record);
});
return recordMap;
}
可以使用 Collectors.groupingby 来简洁地实现:
public static Map<String, List<OneRecord>> buildRecordMapBrief(List<OneRecord> records, List<String> colKeys) {
return records.stream().collect(Collectors.groupingBy(
record -> buildRecordKey(record.getFieldValues(), colKeys)
));
}
很多常用数据处理算法,都可以使用函数式编程的流式计算简洁表达。
更通用的代码
使用函数接口,结合泛型,很容易用精练的代码,写出非常通用的工具方法。 实际应用中,常常会有这样的需求: 有两个对象列表srcList和destList,两个对象类型的某个字段K具有相同的值;需要根据这个相同的值合并对应的两个对象的信息。
这里给出了一个列表合并函数,可以将一个对象列表合并到指定的对象列表中。实现是: 先将待合并的列表srcList根据key值函数keyFunc构建起srcMap,然后遍历dest列表的对象R,将待合并的信息srcMap[key]及T通过合并函数mergeFunc生成的新对象R添加到最终结果列表。
public static <K,R> List<R> mergeList(List<R> srcList, List<R> destList ,
Function<R,K> keyFunc,
BinaryOperator<R> mergeFunc) {
return mergeList(srcList, destList, keyFunc, keyFunc, mergeFunc);
}
public static <T,S,K,R> List<R> mergeList(List<S> srcList, List<T> destList ,
Function<S,K> skeyFunc, Function<T,K> dkeyFunc,
BiFunction<S,T,R> mergeFunc) {
Map<K,S> srcMap = srcList.stream().collect(Collectors.toMap(skeyFunc, s -> s, (k1,k2) -> k1));
return destList.stream().map(
dest -> {
K key = dkeyFunc.apply(dest);
S src = srcMap.get(key);
return mergeFunc.apply(src, dest);
}
).collect(Collectors.toList());
}
更可测的代码
使用函数接口可以方便地隔离外部依赖,使得类和对象的方法更纯粹、更具可测性。博文“使用Java函数接口及lambda表达式隔离和模拟外部依赖更容易滴单测”,“改善代码可测性的若干技巧”集中讨论了如何使用函数接口提升代码的可单测性。
组合的力量
函数编程的强大威力,在于将函数接口组合起来,构建更强大更具有通用性的实用工具方法。超越类型,超越操作与数据的边界。
前面提到,函数接口就是数据转换器。比如Function
, Function
public static <T,S,Q,R> Function<BiFunction<S,Q,R>, Function<T,R>> op(Function<T,S> funcx, Function<T,Q> funcy) {
return opFunc -> aT -> opFunc.apply(funcx.apply(aT), funcy.apply(aT));
}
System.out.println(op(x-> x.toString().length(), y-> y+",world").apply((x,y) -> x+" " +y).apply("hello"));
实现的是 h(t) = h(funx(t), funy(t)) ,h(x,y) 是一个双参数函数。
“Java函数接口实现函数组合及装饰器模式” 展示了如何使用极少量的代码实现装饰器模式,将简单的函数接口组合成更强大功能的复合函数接口。
来看上面的 public static <T,S,K,R> List<R> mergeList(List<S> srcList, List<T> destList , Function<S,K> skeyFunc, Function<T,K> dkeyFunc,BiFunction<S,T,R> mergeFunc)
, 通用性虽好,可是有5个参数,有点丑。怎么改造下呢? 看实现,主要包含两步:1. 将待合并列表转化为 srcMap: map
public static <T,S,K,R> List<R> mergeList(List<S> srcList, List<T> destList ,
Function<S,K> skeyFunc, Function<T,K> dkeyFunc,
BiFunction<S,T,R> mergeFunc) {
return join(destList, mapKey(srcList, skeyFunc)).apply(dkeyFunc, (BiFunction) mergeFunc);
}
public static <T,K> Map<K,T> mapKey(List<T> list, Function<T,K> keyFunc) {
return list.stream().collect(Collectors.toMap(keyFunc, t -> t, (k1,k2) -> k1));
}
public static <T,S,K,R> BiFunction<Function<T,K>, BiFunction<S,T,R>, List<R>> join(List<T> destList, Map<K,S> srcMap) {
return (dkeyFunc,mergeFunc) -> destList.stream().map(
dest -> {
K key = dkeyFunc.apply(dest);
S src = srcMap.get(key);
return mergeFunc.apply(src, dest);
}).collect(Collectors.toList());
}
System.out.println(mergeList(Arrays.asList(1,2), Arrays.asList("an", "a"), s-> s, t-> t.toString().length(), (s,t) -> s+t));
mapKey 是一个通用函数,用于将一个 list 按照指定的 keyFunc 转成一个 Map; join 函数接受一个 list 和待合并的 srcMap, 返回一个二元函数,该函数使用指定的 dkeyFunc 和 mergeFunc 来合并指定数据得到最终的结果列表。这可称之为“延迟指定行为”。现在, mapKey 和 join 都是通用性函数。Amazing !
Java8泛型
在Java8函数式框架的解读中,可以明显看到,泛型无处不在。Java8的泛型推导能力也有很大的增强。可以说,如果没有强大的泛型推导支撑,函数接口的威力将会大打折扣。
完整代码示例
package zzz.study.function;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* Created by shuqin on 17/12/3.
*/
public class FunctionUtil {
public static <T,R> List<R> multiGetResult(List<Function<List<T>, R>> functions, List<T> list) {
return functions.stream().map(f -> f.apply(list)).collect(Collectors.toList());
}
public static <K,R> List<R> mergeList(List<R> srcList, List<R> destList ,
Function<R,K> keyFunc,
BinaryOperator<R> mergeFunc) {
return mergeList(srcList, destList, keyFunc, keyFunc, mergeFunc);
}
public static <T,S,K,R> List<R> mergeList(List<S> srcList, List<T> destList ,
Function<S,K> skeyFunc, Function<T,K> dkeyFunc,
BiFunction<S,T,R> mergeFunc) {
return join(destList, mapKey(srcList, skeyFunc)).apply(dkeyFunc, (BiFunction) mergeFunc);
}
public static <T,K> Map<K,T> mapKey(List<T> list, Function<T,K> keyFunc) {
return list.stream().collect(Collectors.toMap(keyFunc, t -> t, (k1,k2) -> k1));
}
public static <T,S,K,R> BiFunction<Function<T,K>, BiFunction<S,T,R>, List<R>> join(List<T> destList, Map<K,S> srcMap) {
return (dkeyFunc,mergeFunc) -> destList.stream().map(
dest -> {
K key = dkeyFunc.apply(dest);
S src = srcMap.get(key);
return mergeFunc.apply(src, dest);
}).collect(Collectors.toList());
}
/** 对给定的值 x,y 应用指定的二元操作函数 */
public static <T,S,R> Function<BiFunction<T,S,R>, R> op(T x, S y) {
return opFunc -> opFunc.apply(x, y);
}
/** 将两个函数使用组合成一个函数,这个函数接受一个二元操作函数 */
public static <T,S,Q,R> Function<BiFunction<S,Q,R>, R> op(Function<T,S> funcx, Function<T,Q> funcy, T x) {
return opFunc -> opFunc.apply(funcx.apply(x), funcy.apply(x));
}
public static <T,S,Q,R> Function<BiFunction<S,Q,R>, Function<T,R>> op(Function<T,S> funcx, Function<T,Q> funcy) {
return opFunc -> aT -> opFunc.apply(funcx.apply(aT), funcy.apply(aT));
}
/** 将两个函数组合成一个叠加函数, compose(f,g) = f(g) */
public static <T> Function<T, T> compose(Function<T,T> funcx, Function<T,T> funcy) {
return x -> funcx.apply(funcy.apply(x));
}
/** 将若干个函数组合成一个叠加函数, compose(f1,f2,...fn) = f1(f2(...(fn))) */
public static <T> Function<T, T> compose(Function<T,T>... extraFuncs) {
if (extraFuncs == null || extraFuncs.length == 0) {
return x->x;
}
return x -> Arrays.stream(extraFuncs).reduce(y->y, FunctionUtil::compose).apply(x);
}
public static void main(String[] args) {
System.out.println(multiGetResult(
Arrays.asList(
list -> list.stream().collect(Collectors.summarizingInt(x->x)),
list -> list.stream().filter(x -> x < 50).sorted().collect(Collectors.toList()),
list -> list.stream().collect(Collectors.groupingBy(x->(x%2==0? "even": "odd"))),
list -> list.stream().sorted().collect(Collectors.toList()),
list -> list.stream().sorted().map(Math::sqrt).collect(Collectors.toMap(x->x, y->Math.pow(2,y)))),
Arrays.asList(64,49,25,16,9,4,1,81,36)));
List<Integer> list = Arrays.asList(1,2,3,4,5);
Supplier<Map<Integer,Integer>> mapSupplier = () -> list.stream().collect(Collectors.toMap(x->x, y-> y * y));
Map<Integer, Integer> mapValueAdd = list.stream().collect(Collectors.toMap(x->x, y->y, (v1,v2) -> v1+v2, mapSupplier));
System.out.println(mapValueAdd);
List<Integer> nums = Arrays.asList(Arrays.asList(1,2,3), Arrays.asList(1,4,9), Arrays.asList(1,8,27))
.stream().flatMap(x -> x.stream()).collect(Collectors.toList());
System.out.println(nums);
List<Integer> fibo = Arrays.asList(1,2,3,4,5,6,7,8,9,10).stream().collect(new FiboCollector());
System.out.println(fibo);
System.out.println(op(new Integer(3), Integer.valueOf(3)).apply((x,y) -> x.equals(y.toString())));
System.out.println(op(x-> x.length(), y-> y+",world", "hello").apply((x,y) -> x+" " +y));
System.out.println(op(x-> x, y-> y+",world").apply((x,y) -> x+" " +y).apply("hello"));
System.out.println(op(x-> x.toString().length(), y-> y+",world").apply((x,y) -> x+" " +y).apply("hello"));
System.out.println(mergeList(Arrays.asList(1,2), Arrays.asList("an", "a"),
s-> s, t-> t.toString().length(), (s,t) -> s+t));
}
}
小结
本文深入学习了Java8函数式编程框架:Function&Stream&Collector,并展示了函数式编程在实际应用中所带来的诸多益处。函数式编程是一把大锋若钝的奇剑。基于函数接口编程,将函数作为数据自由传递,结合泛型推导能力,可编写出精练、通用、易测的代码,使代码表达能力获得飞一般的提升。
已在知识星球更新源码解析如下:
《精尽 Dubbo 源码解析系列》69 篇。
《精尽 Netty 源码解析系列》61 篇。
《精尽 Spring 源码解析系列》35 篇。
《精尽 MyBatis 源码解析系列》34 篇。
《数据库实体设计》17 篇。
《精尽 Spring MVC 源码解析系列》15 篇。
目前在知识星球更新了《Dubbo 源码解析》目录如下:
01. 调试环境搭建
02. 项目结构一览
03. 配置 Configuration
04. 核心流程一览
05. 拓展机制 SPI
06. 线程池
07. 服务暴露 Export
08. 服务引用 Refer
09. 注册中心 Registry
10. 动态编译 Compile
11. 动态代理 Proxy
12. 服务调用 Invoke
13. 调用特性
14. 过滤器 Filter
15. NIO 服务器
16. P2P 服务器
17. HTTP 服务器
18. 序列化 Serialization
19. 集群容错 Cluster
20. 优雅停机
21. 日志适配
22. 状态检查
23. 监控中心 Monitor
24. 管理中心 Admin
25. 运维命令 QOS
26. 链路追踪 Tracing
... 一共 69+ 篇
目前在知识星球更新了《Netty 源码解析》目录如下:
01. 调试环境搭建
02. NIO 基础
03. Netty 简介
04. 启动 Bootstrap
05. 事件轮询 EventLoop
06. 通道管道 ChannelPipeline
07. 通道 Channel
08. 字节缓冲区 ByteBuf
09. 通道处理器 ChannelHandler
10. 编解码 Codec
11. 工具类 Util
... 一共 61+ 篇
目前在知识星球更新了《数据库实体设计》目录如下:
01. 商品模块
02. 交易模块
03. 营销模块
04. 公用模块
... 一共 17+ 篇
目前在知识星球更新了《Spring 源码解析》目录如下:
01. 调试环境搭建
02. IoC Resource 定位
03. IoC BeanDefinition 载入
04. IoC BeanDefinition 注册
05. IoC Bean 获取
06. IoC Bean 生命周期
... 一共 35+ 篇
目前在知识星球更新了《MyBatis 源码解析》目录如下:
01. 调试环境搭建
02. 项目结构一览
03. MyBatis 面试题合集
04. MyBatis 学习资料合集
05. MyBatis 初始化
06. SQL 初始化
07. SQL 执行
08. 插件体系
09. Spring 集成
... 一共 34+ 篇
源码不易↓↓↓↓↓
点赞支持老艿艿↓↓
以上是关于深度探秘 Java 8 函数式编程(下)的主要内容,如果未能解决你的问题,请参考以下文章