• 何用Java8 Stream API进行数据抽取与收集


    原稿于3.2日发布,然而事情并没完,我发现必须得补充一个常见的坑,为了防止大家采坑,我在更新了本文的后两段。

    上一篇中我们通过一个实例看到了Java8 Stream API 相较于传统的的Java 集合操作的简洁与优势,本篇我们依然借助于一个实际的例子来看看Java8 Stream API 如何抽取及收集数据。
    备注:上一篇内容:如何用Java8 Stream API找到心仪的女朋友

    目标 & 背景

    我们以“处理订单数据”为例,假设我们的应用是一个分布式应用,有"订单应用","物流应用","商品应用”等都是独立的服务。本次我们的目的需要展示订单列表完整数据:

    • 1.查询订单列表。
    • 2.批量查询物流信息。
    • 3.将物流信息填充到订单主信息中。

    假设我们定义了一个订单类,具有几个关键的属性:订单号,状态,订单价,快递信息。如下所示:

    class Order{
        String orderSeq;
        String status;
        double totalPrice;
        String expressInfo;
        // 省略get,set及hashCode等方法
    }    
    

    我们定义了一个快递信息类,几个关键的属性:订单号,物流公司,物流单号,物流状态。如下所示:

    class ExpressInfo{
        String orderSeq;
        String expressName;
        String expressNo;
        String createTime;
        String statusInfo;
        // 省略get,set及hashCode等方法
    }
    

    Java7 实现

    获取订单列表 & 抽取订单号

       List<Order> orderList = getOrderList();
        // 抽取 订单号
        List<String> orderSeqList = new ArrayList<>();
        for (Order order : orderList) {
            orderSeqList.add(order.getOrderSeq());
        }
    

    这里我们获取了订单列表orderList,此时expressInfo里边是没有数据的。这里抽取单号依然是Java传统的写法。

    批量查询快递信息 & 组装 订单-快递信息 map

    由于我们是通过调用远程服务来获取快递信息,为了减少网络通信次数,我们采取批量查询的方式。这也是为什么,上一步中我们要抽取订单号
    下面我们来获取快递信息

    // 调用远程服务,
    List<ExpressInfo> expressInfos = RpcGetExpressInfoBatch(orderSeqList);
    // 组装 订单-快递 关系map
    Map<String,String> orderExpressMap = new HashMap<>();
    for(ExpressInfo e: expressInfos){
        orderExpressMap.put(e.getOrderSeq(),e.getStatusInfo());
    }
    

    这里组装map,也依然是Java7常用的写法。

    组合数据,将快递信息填充进订单

    for(Order order:orderList){
        String expressInfo = orderExpressMap.get(order.getOrderSeq());
        order.setExpressInfo(expressInfo);
    }
    

    至此,我们使用Java7 的写法,完成了开篇设定的目标。下面我们看Java8的写法

    Java8 实现

    获取订单列表 & 抽取订单号

    // 获取列表
    List<Order> orderList = getOrderList();
    // 抽取单号
    List<String> orderSeqs = orderList.stream()
            .map(Order::getOrderSeq)
            .collect(Collectors.toList());
    

    这里我们使用了stream.map,在map()中,我们的写法是Order::getOrderSeq表示调用Order对象的getOrderSeq()方法来抽取订单号。
    这里的::叫“方法引用”,是Java8中的新写法。
    map()后面紧跟的是collect收集器,他将抽取的数据toList(),于是我们得到了最终的List

    批量查询快递信息 & 组装 订单-快递信息 map

    下面我们仍然是通过远程调用来获取快递信息,然后使用Java8的语法建立一个 订单-快递 关联信息的map。

    List<ExpressInfo> expressInfos = RpcGetExpressInfoBatch(orderSeqList);
    Map<String,String> orderExpressMap =expressInfos.stream()
            .collect(Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo));
    

    这里代码比Java7的要少吧,且一目了然,这里用strean().collect来收集数据,收集成什么形式呢?看名知意,Collectors.toMap收集成Map,收集什么数据呢?toMap()中,写了ExpressInfo::getOrderSeqExpressInfo::getStatusInfo,即:抽取orderSeq作为key,statusInfo作为value。

    通过这里的Collectors.toMap收集器,我们很方便的获得了 订单-物流关系数据map

    组合数据,将快递信息填充进订单

    经过上面的两步,我们得到了符合我们要求的数据,现在我们需要将快递信息填充进订单,代码如下:

    orderList.stream().forEach(o -> o.setExpressInfo(orderExpressMap.get(o.getOrderSeq())));
    

    你没看错,就只有这么一行。

    补充说明: key 重复的处理

    上面的代码中,我们使用如下代码来从list中收集Map:

    Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo))
    

    这里如果expressInfos这个list中有重复数据,那么orderSeq就会有重复的,这种情况下就会报一个错:

    java.lang.IllegalStateException: Duplicate key
    想模拟这个错误很简单,往expressInfos中add两条一样的数据,运行上面的抽取map代码就会报错。
    为了解决这个问题,我们需要这样写:

    List<ExpressInfo> expressInfos = RpcGetExpressInfoBatch(orderSeqList);
     Map<String,String> orderExpressMap =expressInfos.stream()
              .collect(Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo,(v1,v2)->v1));
    

    这里,我们主要的变动是这一句:

    Collectors.toMap(ExpressInfo::getOrderSeq,ExpressInfo::getStatusInfo,(v1,v2)->v1)
    

    我们在在Collectors.toMap中增加了一个lumda表达式:

    (v1,v2)->v1
    

    意思是当有重复数据v1,v2出现的时候,我们选择v1。当然,你可以根据你的情况选择v2。
    选择v1,或者v2,需要根据你的业务来权衡。比如list中的数据按照时间先后排序的,我们取最新的就选v2,反之取v1。

    总结

    本节,我们使用Java8 Stream API,完成了数据的抽取和收集,使用了map(),和collect()来完成数据的抽取和收集,并了解了两种收取方式toListtoMap。同时我们也知道应对list中有重复数据导致报错的问题。所以,以后如果有人问你"Java8 stream 如何获取对象的某个属性list啊?",“java8 stream 如何获取指定数据组装成map啊?”,你就可以把本文中的方法告诉他了。

    除此之外,Java8 Streap API 还有分组 等功能,后面再说。你也可以关注我的公众号,第一时间收到推送。

    alt 逃离沙漠公众号

    Java8系列- 如何用Java8 Stream API找到心仪的女朋友
    SpringMVC是怎么工作的,SpringMVC的工作原理
    Mybatis Mapper接口是如何找到实现类的-源码分析
    小程序云开发:菜鸟也能全栈做产品
    CORS详解,CORS原理分析
    工作6年,失业19天

    Docker & k8s 系列一:快速上手docker
    Docker & k8s 系列二:本机k8s环境搭建
    Docker & k8s 系列三:在k8s中部署单个服务实例

  • 相关阅读:
    SpringMVC防止重复提交
    Apache Lucene初探
    ORACLE触发器详解
    ORA-02287: sequence number not allowed here问题的解决
    数据库索引
    字符串 栈
    字符串 逆序
    汽水瓶
    查找 排序----有一只兔子,从出生后第3个月起每个月都生一只兔子,小兔子长到第三个月后每个月又生一只兔子,假如兔子都不死,问每个月的兔子总数为多少?
    usb串口的作用以及JLINK
  • 原文地址:https://www.cnblogs.com/demingblog/p/12399043.html
Copyright © 2020-2023  润新知