Java8函数式编程真有这么神奇?- [完结篇]不得不学的Collector与map...
本文是java8函数式编程精讲stream最后一节,一般最后一节都是拔高的重头戏。
我会重点讲解一下Stream中收集器Collector的主要使用方式。这也是实际编程中经常使用到的编程技巧和语法糖。无论是从实用性还是从知识扩展角度,掌握它的性价比都非常高。
废话不多说,直接进入正题。
收集器
收集器(Collector)的作用为:将流中元素累积成一个结果作用于终端操作collect()上
关于收集器,我们主要关注:
collect()(操作实现Collector接口的集合)、
Collector(接口)
Collectors(工具类)
日常开发中,我们使用最多的是预定义收集器功能(Collectors)
它的作用主要是:
将流元素规约和汇总为一个值
将流元素分组
将流元素分区
Collect()方法解析
R collect(Supplier supplier, 初始化结果容器
BiConsumer accumulator, 添加元素到结果容器的逻辑
BiConsumer combiner); 并行执行时多个结果容器的合并方式
我们此处介绍几个常用的预定义收集器方法
集合收集器
@Test
public void toList() {
List list = CartService.getCartSkuList();
List result = list.stream()
.filter(sku -> sku.getTotalPrice() > 100)
.collect(Collectors.toList());
System.out.println(JSON.toJSONString(result, true));
}
这个用例的目的是:选出总价大于100的商品并打印,也就是大于100的sku会被保留。
运行结果:
[
{
"skuCategory":"ELECTRONICS",
"skuId":2,
"skuName":"无人机",
"skuPrice":1000.0,
"totalNum":10,
"totalPrice":1000.0
},
{
"skuCategory":"ELECTRONICS",
"skuId":1,
"skuName":"VR一体机",
"skuPrice":2100.0,
"totalNum":10,
"totalPrice":2100.0
},
{
"skuCategory":"CLOTHING",
"skuId":13,
"skuName":"衬衫",
"skuPrice":120.0,
"totalNum":10,
"totalPrice":120.0
}
]
Process finished with exit code 0
集合分组
@Test
public void group() {
List list = CartService.getCartSkuList();
// key=分组条件 value=元素集合 即Map<分组条件,结果集合>
Map> result = list.stream()
.collect(Collectors.groupingBy(sku -> sku.getSkuCategory()));
System.out.println(JSON.toJSONString(result, true));
}
❝这个用例的目的是:根据sku类别对list进行分组,分组结束后返回一个Map<分组条件,结果集合>,即key=分组条件 value=元素集合
❞
运行结果:
{"CLOTHING":[
{
"skuCategory":"CLOTHING",
"skuId":4,
"skuName":"牛仔裤",
"skuPrice":60.0,
"totalNum":10,
"totalPrice":60.0
},
{
"skuCategory":"CLOTHING",
"skuId":13,
"skuName":"衬衫",
"skuPrice":120.0,
"totalNum":10,
"totalPrice":120.0
}
],"BOOKS":[
{
"skuCategory":"BOOKS",
"skuId":121,
"skuName":"Java编程思想",
"skuPrice":100.0,
"totalNum":10,
"totalPrice":100.0
},
{
"skuCategory":"BOOKS",
"skuId":3,
"skuName":"程序化广告",
"skuPrice":80.0,
"totalNum":10,
"totalPrice":80.0
}
],"ELECTRONICS":[
{
"skuCategory":"ELECTRONICS",
"skuId":2,
"skuName":"无人机",
"skuPrice":1000.0,
"totalNum":10,
"totalPrice":1000.0
},
{
"skuCategory":"ELECTRONICS",
"skuId":1,
"skuName":"VR一体机",
"skuPrice":2100.0,
"totalNum":10,
"totalPrice":2100.0
}
]
}
从运行结果我们可以看出满足要求。
集合分区
集合分区是分组的一种特例:
❝分区是由一个谓词作为分区函数,分区函数返回一个boolean值最终将分区结果分为两组,一组为boolean=true的 一组为boolean=false的通俗的说也就是满足条件的分为一组,不满足条件的为一组
❞
@Test
public void partition() {
List list = CartService.getCartSkuList();
Map> partition = list.stream()
.collect(Collectors.partitioningBy(sku -> sku.getTotalPrice() > 100));
System.out.println(JSON.toJSONString(partition, true));
}
运行结果:
{
false:[
{
"skuCategory":"CLOTHING",
"skuId":4,
"skuName":"牛仔裤",
"skuPrice":60.0,
"totalNum":10,
"totalPrice":60.0
},
{
"skuCategory":"BOOKS",
"skuId":121,
"skuName":"Java编程思想",
"skuPrice":100.0,
"totalNum":10,
"totalPrice":100.0
},
{
"skuCategory":"BOOKS",
"skuId":3,
"skuName":"程序化广告",
"skuPrice":80.0,
"totalNum":10,
"totalPrice":80.0
}
],
true:[
{
"skuCategory":"ELECTRONICS",
"skuId":2,
"skuName":"无人机",
"skuPrice":1000.0,
"totalNum":10,
"totalPrice":1000.0
},
{
"skuCategory":"ELECTRONICS",
"skuId":1,
"skuName":"VR一体机",
"skuPrice":2100.0,
"totalNum":10,
"totalPrice":2100.0
},
{
"skuCategory":"CLOTHING",
"skuId":13,
"skuName":"衬衫",
"skuPrice":120.0,
"totalNum":10,
"totalPrice":120.0
}
]
}
Process finished with exit code 0
通过日志打印我们能够看出,根据是否满足断言将集合分为两个组。结果与分组很像,只不过key变成了true/false。
流高级操作
接下来是Java8函数式编程系列的最后一个章节,到此我们的Stream相关的讲解就暂时告一段落。
❝本节中我将带领读者朋友一起学习一下Stream高级编程相关的知识。
❞
规约与汇总
Stream操作中有两个相对高阶的概念,分别为规约和汇总。
规约(reduce)
将Stream流中元素转换成一个值
汇总(collect)
将Stream流中的元素转换成一个容器,如Map 、List 、 Set
上文我们已经讲过了汇总操作Collect,此处我们重点讲讲规约操作(reduce)
「对reduce的理解」
❝reduce 操作可以实现从Stream中生成一个值,其生成的值不是随意的,是根据指定的计算模型。
❞
❝比如,之前提到count、min和max方法,因为常用而被纳入标准库中。事实上,这些方法都是reduce操作。
❞
我们看一个案例
/**
* reduce 案例1:
* 计算一批商品的总价格
*/
@Test
public void reduceTest() {
/**
* 准备一批订单数据
*/
List list = Lists.newArrayList();
list.add(new Order(1, 2, 15.12));
list.add(new Order(2, 5, 257.23));
list.add(new Order(3, 3, 23331.12));
/**
* 传统方式
* 1. 计算商品数量
* 2. 计算消费总金额
*
* 以下展示Stream的reduce方式
* 思想:分治法
*
* U reduce(U identity, 初始基点,此处就是订单中属性都是0
* BiFunction accumulator, 计算逻辑,定义两个元素如何进行操作
* BinaryOperator combiner); 并行执行时多个部分结果的合并方式
*
*/
/**
* 汇总商品数量和总金额
*/
Order order = list.stream()
//.parallel() // 并行方式
.reduce(
// 参数1:初始化值
new Order(0, 0, 0.0),
// 参数2:Stream中两个元素的计算逻辑
(Order order1, Order order2) -> {
System.out.println("执行 计算逻辑 方法!!!");
// 计算两个订单商品数量和,消费金额之和
int productCount = order1.getProductCount() + order2.getProductCount();
double totalAmount = order1.getTotalAmount() + order2.getTotalAmount();
// 返回计算结果
return new Order(0, productCount, totalAmount);
},
// 参数3:并行情况下,多个并行结果如何合并
(Order order1, Order order2) -> {
System.out.println("执行 合并 方法!!!");
// 计算两个订单商品数量和,消费金额之和
int productCount = order1.getProductCount() + order2.getProductCount();
double totalAmount = order1.getTotalAmount() + order2.getTotalAmount();
// 返回计算结果
return new Order(0, productCount, totalAmount);
});
System.out.println(JSON.toJSONString(order, true));
}
}
运行结果:
执行 计算逻辑 方法!!!
执行 计算逻辑 方法!!!
执行 计算逻辑 方法!!!
{
"id":0,
"productCount":10,
"totalAmount":23603.469999999998
}
可见通过reduce逻辑,我们能够很轻松实现注入复杂条件的累加,求最值等操作。
reduce规约操作,实际上采用了分治思想,提升了编码和执行效率。
更多关于reduce的解析可以参考 https://blog.csdn.net/weixin_41835612/article/details/83687078
4.9 Stream特点:
通过上述的介绍,我们能够总结出Stream的特点:
「无存储」。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
「为函数式编程而生」。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。
「惰式执行」。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
「可消费性」。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。
更多Java8特性
由于篇幅及笔者个人能力有限,不能将Java8的所有特性都详细的呈现,感兴趣的同学可以自行学习。
Optional
接口默认方法
新的日期和时间API
CompletableFuture:组合式异步编程
G1垃圾回收器
推荐阅读
《Java8实战》Java8 In Action中文版
《深入理解JVM&G1 GC》
总结
到此,针对Java8的新特性Lambda表达式及stream流式编程的讲解就告一段落,希望本系列对读者朋友们有所帮助。
这不是结束,这也不是开始,这是结束的开始。