乐字节Java8核心特性实战之Stream流

4次阅读

共计 10860 个字符,预计需要花费 28 分钟才能阅读完成。

大家好,我是乐字节的小乐。说起流,我们会联想到手机、电脑组装流水线, 物流仓库商品包装流水线等等, 如果把手机 , 电脑, 包裹看做最终结果的话, 那么加工商品前的各种零部件就可以看做数据源,而中间一系列的加工作业操作,就可以看做流的处理。

一、概念

Java Se 中对于流的操作有输入输出 IO 流, 而 Java8 中引入的 Stream 属于 Java API 中的一个新成员,它允许你以声明性方式处理数据集合,Stream 使用一种类似 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。注意这里的流操作可以看做是对集合数据的处理。

简单来说, 流是一种数据渠道, 用于操作数据源 (集合、数组、文件等) 所生产的元素序列。

  • 源 - 流会使用一个提供数据的源, 如集合、数组或输入 | 输出资源。

    从有序集生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致

  • 元素序列 - 就像集合一样,流也提供了一个接口, 可以访问特定元素类型的一组有序值。
  • 数据处理操作 - 流的数据处理功能支持类似于数据库的操作(数据筛选、过滤、排序等操作)。
  • 流水线 - 多个流操作本身会返回一个流,多个操作就可以链接起来, 成为数据处理的一道流水线。

二、流 & 集合

  • 计算的时期

集合中数据都是计算完毕的数据,例如从数据库中查询用户记录 按用户 id 查询 降序排列 然后通过 list 接收用户记录,数据的计算已在放入集合前完成

流中数据按需计算,按照使用者的需要计算数据,例如通过搜索引擎进行搜索,搜索出来的条目并不是全部呈现出来的,而且先显示最符合的前 10 条或者前 20 条,只有在点击“下一页”的时候,才会再输出新的 10 条。流的计算也是这样,当用户需要对应数据时,Stream 才会对其进行计算处理。

  • 外部迭代与内部迭代

把集合比作一个工厂的仓库的话,一开始工厂硬件比较落后,要对货物作什么修改,此时工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物转运到另一个仓库中。此时对于开发者来说需要亲自去做迭代,一个个地找到需要的货物,并进行处理,这叫做外部迭代。

当工厂发展起来后,配备了流水线作业,工厂只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。相当于 Java8 引入的 Stream 对数据的处理实现了”自动化”操作。

三、流操作过程

整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。需要注意的是: 很多流操作本身就会返回一个流,所以多个操作可以直接连接起来,如下图这样,操作可以进行链式调用,并且并行流还可以实现数据流并行处理操作。

总的来说,流操作过程分为三个阶段:

  • 创建

    借助数据源创建流对象

  • 中间处理

    筛选、切片、映射、排序等中间操作

  • 终止流

    匹配、汇总、分组等终止操作

四、流的创建

对流操作首先要创建对应的流,流的创建集中形式如下:

4.1 集合创建流

在 Java 8 中, 集合接口有两个方法来生成流:

  • stream() − 为集合创建串行流。
  • parallelStream() − 为集合创建并行流。

示例代码如下:

public static void main(String[] args) {
    /**
         * 定义集合 l1 并为集合创建串行流
         */
    List<String> l1 = Arrays.asList("周星驰", "周杰伦", "周星星", "周润发");
    // 返回串行流
    l1.stream();
    // 返回并行流
    l1.parallelStream();}

上述操作得到的流是通过原始数据转换过来的流,除了这种流创建的基本操作外,对于流的创建还有以下几种方式。

4.2 值创建流

Stream.of(T...):Stream.of("aa", "bb") 生成流

// 值创建流 生成一个字符串流
Stream<String> stream = Stream.of("java8", "Spring", "SpringCloud");
stream.forEach(System.out::println);

4.3 数组创建流

根据参数的数组类型创建对应的流。

  • Arrays.stream(T[])
  • Arrays.stream(int[])
  • Arrays.stream(double[])
  • Arrays.stream(long[])

    /**

    • 这里以 int 为例 long double 不再举例
 */
 Stream stream = Arrays.stream(Arrays.asList(10, 20, 30, 40).toArray());
 // 根据数组索引范围创建指定 Stream
 stream = Arrays.stream(Arrays.asList(10, 20, 30, 40).toArray(), 0, 2);

4.4 文件生成流

stream = Files.lines(Paths.get("C:\\java\\jdbc.properties"));
System.out.println(stream.collect(Collectors.toList()));
// 指定字符集编码
stream = Files.lines(Paths.get("C:\\java\\jdbc.properties"), Charset.forName("utf-8"));
System.out.println(stream.collect(Collectors.toList()));

4.5 函数生成流

两个方法:

  • iterate:依次对每个新生成的值应用函数
  • generate:接受一个函数,生成一个新的值

    // 重 100 开始 生成偶数流
    Stream.iterate(100, n -> n + 2);
    // 产生 1 -100 随机数
    Stream.generate(() ->(int) (Math.random() * 100 + 1));

五、流中间操作

流的中间操作分为三大类: 筛选切片、映射、排序。

筛选切片: 类似 sql 中 where 条件判断的意思,对元素进行筛选操作

映射: 对元素结果进行转换,优点类似 select 字段意思或者对元素内容进行转换处理

排序: 比较好理解,常用 sql 中按字段升序 降序操作

流中间操作数据准备(这里以订单数据处理为例)

@Data
public class Order {
    // 订单 id
    private Integer id;
    // 订单用户 id
    private Integer userId;
    // 订单编号
    private String orderNo;
    // 订单日期
    private Date orderDate;
    // 收货地址
    private String address;
    // 创建时间
    private Date createDate;
    // 更新时间
    private Date updateDate;
    // 订单状态  0- 未支付  1- 已支付  2- 待发货  3- 已发货  4- 已接收  5- 已完成
    private Integer status;
    // 是否有效  1- 有效订单  0- 无效订单
    private Integer isValid;
    // 订单总金额
    private  Double total;
  }

Order order01 = new Order(1, 10, "20190301",
                new Date(), "上海市 - 浦东区", new Date(), new Date(), 4, 1, 100.0);
Order order02 = new Order(2, 30, "20190302",
                    new Date(), "北京市四惠区", new Date(), new Date(), 1, 1, 2000.0);
Order order03 = new Order(3, 20, "20190303",
                    new Date(), "北京市 - 朝阳区", new Date(), new Date(), 4, 1, 500.0);
Order order04 = new Order(4, 40, "20190304",
                    new Date(), "北京市 - 大兴区", new Date(), new Date(), 4, 1, 256.0);
Order order05 = new Order(5, 40, "20190304",
                    new Date(), "上海市 - 松江区", new Date(), new Date(), 4, 1, 1000.0);
ordersList = Arrays.asList(order01, order02, order03, order04, order05);


六、筛选 & 切片

  • 筛选有效订单

    // 过滤有效订单
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .forEach(System.out::println);
    
  • 筛选有效订单 取第一页数据(每页 2 条记录)

    // 过滤有效订单 取第一页数据(每页 2 条记录)
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

     .limit(2)
     .forEach(System.out::println);
    
  • 筛选订单集合有效订单 取最后一条记录

    // 过滤订单集合有效订单 取最后一条记录
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .skip(ordersList.size() - 2)  // 跳过前 ordersList.size()-2 记录
    .forEach(System.out::println);
    
  • 筛选有效订单 取第 3 页数据(每页 2 条记录)

    // 过滤有效订单 取第 3 页数据(每页 2 条记录) 并打印到控制台
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .skip((3 - 1) * 2)
    .limit(2)
    .forEach(System.out::println);
    
  • 筛选无效订单去除重复订单号记录

    // 过滤无效订单 去除重复订单号记录 重写 Order equals 与 hashCode 方法
    ordersList.stream().filter((order) -> order.getIsValid() == 0)

    .distinct()
    .forEach(System.out::println);
    

七、映射

  • 过滤有效订单, 获取所有订单编号

    // 过滤有效订单,获取所有订单编号
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .map((order) -> order.getOrderNo())
    .forEach(System.out::println);
    
  • 过滤有效订单 , 并分离每个订单下收货地址市区信息

    ordersList.stream().map(o -> o.getAddress()

    .split("-"))
    .flatMap(Arrays::stream)
    .forEach(System.out::println);
    

八、排序

  • 过滤有效订单 根据用户 id 进行排序

    // 过滤有效订单 根据用户 id 进行排序
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .sorted((o1, o2) -> o1.getUserId() - o2.getUserId())
    .forEach(System.out::println);    
    

    // 或者等价写法
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .sorted(Comparator.comparingInt(Order::getUserId))
    .forEach(System.out::println);
    
  • 过滤有效订单 , 根据订单状态排序 如果订单状态相同根据订单创建时间排序

    // 过滤有效订单 如果订单状态相同 根据订单创建时间排序 反之根据订单状态排序
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .sorted((o1, o2) -> {if (o1.getStatus().equals(o2.getStatus())) {return o1.getCreateDate().compareTo(o2.getCreateDate());
        } else {return o1.getStatus().compareTo(o2.getStatus());
    }})
    .forEach(System.out::println);
    
    

    // 等价形式
    ordersList.stream().filter((order) -> order.getIsValid() == 1)

    .sorted(Comparator.comparing(Order::getCreateDate)
    .thenComparing(Comparator.comparing(Order::getStatus)))
    .forEach(System.out::println);
    

九、流的终止操作

终止操作会从流的流水线生成结果。其结果是任何不是流的值,比如常见的 List、Integer,甚 至 void 等结果。对于流的终止操作,分为以下三类:

十、查找与匹配

  • 筛选有效订单 匹配是否全部为已支付订单

    // 筛选有效订单 匹配是否全部为已支付订单
    System.out.println(“allMatch 匹配结果:” +

                     ordersList.stream()
                           .filter((order) -> order.getIsValid() == 1)
                        .allMatch((o) -> o.getStatus() != 0)
                  );
    
  • 筛选有效订单 匹配是否存在未支付订单

    // 筛选有效订单 匹配是否存在未支付订单
    System.out.println(“anyMatch 匹配结果:” +

                     ordersList.stream()
                           .filter((order) -> order.getIsValid() == 1)
                           .anyMatch((o) -> o.getStatus() == 0)
                  );
    
  • 筛选有效订单 全部未完成订单

    // 筛选有效订单 全部未完成订单
    System.out.println(“noneMatch 匹配结果:” +

                    ordersList.stream()
                        .filter((order) -> order.getIsValid() == 1)
                        .noneMatch((o) -> o.getStatus() == 5)
                  );
    
  • 筛选有效订单 返回第一条订单

    // 筛选有效订单 返回第一条订单

        System.out.println("findAny 匹配结果:"+
                               ordersList.stream()
                                  .filter((order) -> order.getIsValid() == 1)
                                  .findAny()
                                    .get());
    
  • 筛选所有有效订单 返回订单总数

    // 筛选所有有效订单 返回订单总数

        System.out.println("count 结果:" + 
                            ordersList.stream()
                                .filter((order) -> order.getIsValid() == 1)
                                .count());
    
  • 筛选有效订单 返回金额最大订单金额

    // 筛选有效订单 返回金额最大订单金额

        System.out.println("订单金额最大值:" + 
                               ordersList.stream()
                                .filter((order) -> order.getIsValid() == 1)
                                .map(Order::getTotal)
                                .max(Double::compare)
                                .get());
    
  • 筛选有效订单 返回金额最小订单金额

    // 筛选有效订单 返回金额最小订单金额

        System.out.println("订单金额最小值:" + 
                                   ordersList.stream()
                                    .filter((order) -> order.getIsValid() == 1)
                                    .map(Order::getTotal)
                                    .min(Double::compare)
                                    .get());
    

十一、归约 & 收集

11.1 归约

将流中元素反复结合起来,得到一个值的操作
  • 计算有效订单总金额

    // 计算有效订单总金额

        System.out.println("有效订单总金额:" + 
                            ordersList.stream()
                                .filter((order) -> order.getIsValid() == 1)
                                .map(Order::getTotal)
                                .reduce(Double::sum)
                                .get());
    

11.2 Collector 数据收集

将流转换为其他形式,coollect 方法作为终端操作,接收一个 Collector 接口的实现,用于给 Stream 中元素做汇总的方法。最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中

11.3 集合收集

常用集合收集方法 toList、toSet、toCollection、toMap 等

  • 筛选所有有效订单 并收集订单列表

    // 筛选所有有效订单并收集订单列表
    ordersList.stream()

    .filter((order) -> order.getIsValid() == 1)
    .collect(Collectors.toList())
    .forEach(System.out::println);
    
  • 筛选所有有效订单并收集订单号与订单金额

    // 筛选所有有效订单 并收集订单号 与 订单金额
    Map<String,Double> map=ordersList.stream().filter((order) -> order.getIsValid() == 1).

    collect(Collectors.toMap(Order::getOrderNo, Order::getTotal));

    // java8 下对 map 进行遍历操作 如果 Map 的 Key 重复, 会报错
    map.forEach((k,v)->{

    System.out.println("k:"+k+":v:"+v);

    });

十二、汇总

汇总操作在 Stream 流操作比较常见,比如计算总数,求平均等操作,常用方法如下:

相关操作如下

  • 筛选所有有效订单 返回订单总数

    System.out.println(“count 结果:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .collect(Collectors.counting())
                  );

    System.out.println(“count 结果:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .count());
    
  • 返回订单总金额

    System.out.println(“ 订单总金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .collect(Collectors.summarizingDouble(Order::getTotal))
                  );

    System.out.println(“ 订单总金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .mapToDouble(Order::getTotal)
                               .sum());

    System.out.println(“ 订单总金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .map(Order::getTotal)
                               .reduce(Double::sum)
                               .get());
    
  • 返回用户 id=20 有效订单平均每笔消费金额

    System.out.println(“ 用户 id=20 有效订单平均每笔消费金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .filter((order -> order.getUserId()==20))
                               .collect(Collectors.averagingDouble(Order::getTotal))
                  );

    System.out.println(“ 用户 id=20 有效订单平均每笔消费金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .filter((order -> order.getUserId()==20))
                               .mapToDouble(Order::getTotal)
                               .average()
                               .getAsDouble());
    

    System.out.println(“ 用户 id=20 有效订单平均每笔消费金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .filter((order -> order.getUserId()==20))
                               .collect(Collectors.summarizingDouble(Order::getTotal))
                               .getAverage());
    
  • 筛选所有有效订单 并计算订单总金额

    System.out.println(“ 订单总金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .collect(Collectors.summingDouble(Order::getTotal))
                  );
    

十三、最值

  • 筛选所有有效订单 并计算最小订单金额

    System.out.println(“ 最小订单金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .map(Order::getTotal)
                               .collect(Collectors.minBy(Double::compare))
                  );
    
  • 筛选所有有效订单 并计算最大订单金额

    // 筛选所有有效订单 并计算最大订单金额
    System.out.println(“ 最大订单金额:”+

                           ordersList.stream()
                               .filter((order) -> order.getIsValid() == 1)
                               .map(Order::getTotal)
                               .collect(Collectors.maxBy(Double::compare))
                  );
    

十四、分组 & 分区

14.1 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型,groupingBy 第二参数用于实现多级分组

  • 根据有效订单支付状态进行分组操作

    Map<Integer,List<Order>> g01=ordersList.stream()

                                .filter((order) -> order.getIsValid() == 1)
                                .collect(Collectors.groupingBy(Order::getStatus));

    g01.forEach((status,order)->{

    System.out.println("----------------");
    System.out.println("订单状态:"+status);
    order.forEach(System.out::println);

    });

  • 筛选有效订单,根据用户 id 和 支付状态进行分组

    Map<Integer,Map<String,List<Order>>> g02= ordersList.stream()

                    .filter((order) -> order.getIsValid() == 1)
                    .collect(Collectors.groupingBy(Order::getUserId,
                              Collectors.groupingBy((o)->{if(o.getStatus()==0){return "未支付";}else if (o.getStatus()==1){return "已支付";}else if (o.getStatus()==2){return "待发货";}else if (o.getStatus()==3){return "已发货";}else if (o.getStatus()==4){return "已接收";} else{return "已完成";}
                                }
                                ))
                            );

    g02.forEach((userId,m)->{

    System.out.println("用户 id:"+userId+"--> 有效订单如下:");
    m.forEach((status,os)->{System.out.println("状态:"+status+"--- 订单列表如下:");
        os.forEach(System.out::println);
    });
    System.out.println("-----------------------");

    });

14.2 分区

分区与分组的区别在于,分区是按照 true 和 false 来分的,因此 partitioningBy 接受的参数的 lambda 也是 T -> boolean

  • 分区操作 - 筛选订单金额 >1000 的有效订单

    Map<Boolean,List<Order>> g03= ordersList.stream()

            .filter((order) -> order.getIsValid() == 1)
            .collect(Collectors.partitioningBy((o)->o.getTotal()>1000));

    g03.forEach((b,os)->{

    System.out.println("分区结果:"+b+"-- 列表结果:");
    os.forEach(System.out::println);

    });

  • 拼接操作 - 筛选有效订单并进行拼接

    String orderStr=ordersList.stream()

            .filter((order) -> order.getIsValid() == 1)
            .map(Order::getOrderNo)
            .collect(Collectors.joining(","));

    System.out.println(orderStr);

十五、流的应用

Java8 引入 Stream 流操作,使得对元素的处理更加的方便快捷,通过 Stream 提供的相关方法很好的结合 Lambda、函数式接口、方法引用等相关内容,使得流的处理相比较原始集合处理代码极大简化,Stream 支持函数的链式调用,代码上更加紧凑同时 Stream 支持的元素的并行化处理提高了程序的执行性能。对于 Stream 流的应用通常在集合元素数据处理上特别是对元素需要进行多次处理的情况,同时对于函数式编程的味道更加浓重,也是以后开发的一个趋势。

好了,Java8 核心特性之 Stream 流就介绍到这里了,应该是非常详尽了,希望大家喜欢,多多关注小乐,小乐将会给大家带来更多的技干货。乐字节祝大家端午快乐,学习快乐!

正文完
 0