Java8函数式编程真有这么神奇?- [完结篇]不得不学的Collector与map...

分布式朝闻道

共 8802字,需浏览 18分钟

 · 2022-06-10

本文是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流式编程的讲解就告一段落,希望本系列对读者朋友们有所帮助。

这不是结束,这也不是开始,这是结束的开始。


浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报