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

流的概念

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

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

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

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

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

流 & 集合

  • 计算的时期

Java8 - Stream - 图1

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

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

  • 外部迭代与内部迭代

Java8 - Stream - 图2

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

当工厂发展起来后,配备了流水线作业,工厂只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。

这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。相当于 Java8 引入的 Stream 对数据的处理实现了” 自动化” 操作。

流操作过程

Java8 - Stream - 图3

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

Java8 - Stream - 图4

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

  • 创建

借助数据源创建流对象

  • 中间处理

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

  • 终止流

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

流的创建

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

Java8 - Stream - 图5

1 集合创建流

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

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

示例代码如下:

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

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

2 值创建流

Stream.of(T…) : Stream.of(“aa”, “bb”) 生成流

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

3 数组创建流

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

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

4 文件生成流

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

5 函数生成流

两个方法:

  • iterate : 依次对每个新生成的值应用函数
  • generate :接受一个函数,生成一个新的值
  1. // 重100 开始 生成偶数流
  2. Stream.iterate(100, n -> n + 2);
  3. // 产生1-100 随机数
  4. Stream.generate(() ->(int) (Math.random() * 100 + 1));

流中间操作

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

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

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

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

Java8 - Stream - 图6

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

  1. @Data
  2. public class Order {
  3. // 订单id
  4. private Integer id;
  5. // 订单用户id
  6. private Integer userId;
  7. // 订单编号
  8. private String orderNo;
  9. // 订单日期
  10. private Date orderDate;
  11. // 收货地址
  12. private String address;
  13. // 创建时间
  14. private Date createDate;
  15. // 更新时间
  16. private Date updateDate;
  17. // 订单状态 0-未支付 1-已支付 2-待发货 3-已发货 4-已接收 5-已完成
  18. private Integer status;
  19. // 是否有效 1-有效订单 0-无效订单
  20. private Integer isValid;
  21. //订单总金额
  22. private Double total;
  23. }
  24. Order order01 = new Order(1, 10, "20190301", new Date(), "上海市-浦东区", new Date(), new Date(), 4, 1, 100.0);
  25. Order order02 = new Order(2, 30, "20190302", new Date(), "北京市四惠区", new Date(), new Date(), 1, 1, 2000.0);
  26. Order order03 = new Order(3, 20, "20190303", new Date(), "北京市-朝阳区", new Date(), new Date(), 4, 1, 500.0);
  27. Order order04 = new Order(4, 40, "20190304", new Date(), "北京市-大兴区", new Date(), new Date(), 4, 1, 256.0);
  28. Order order05 = new Order(5, 40, "20190304", new Date(), "上海市-松江区", new Date(), new Date(), 4, 1, 1000.0);
  29. ordersList = Arrays.asList(order01, order02, order03, order04, order05);

筛选 & 切片

  • 筛选有效订单

    1. // 过滤有效订单
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .forEach(System.out::println);
  • 筛选有效订单 取第一页数据 (每页 2 条记录)

    1. // 过滤有效订单 取第一页数据(每页2条记录)
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .limit(2)
    4. .forEach(System.out::println);
  • 筛选订单集合有效订单 取最后一条记录

    1. // 过滤订单集合有效订单 取最后一条记录
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .skip(ordersList.size() - 2) // 跳过前ordersList.size()-2 记录
    4. .forEach(System.out::println);
  • 筛选有效订单 取第 3 页数据 (每页 2 条记录)

    1. // 过滤有效订单 取第3页数据(每页2条记录) 并打印到控制台
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .skip((3 - 1) * 2)
    4. .limit(2)
    5. .forEach(System.out::println);
  • 筛选无效订单去除重复订单号记录

    1. // 过滤无效订单 去除重复订单号记录 重写Order equals 与 hashCode 方法
    2. ordersList.stream().filter((order) -> order.getIsValid() == 0)
    3. .distinct()
    4. .forEach(System.out::println);

映射

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

    1. //过滤有效订单,获取所有订单编号
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .map((order) -> order.getOrderNo())
    4. .forEach(System.out::println);
  • 过滤有效订单 , 并分离每个订单下收货地址市区信息

    1. ordersList.stream().map(o -> o.getAddress()
    2. .split("-"))
    3. .flatMap(Arrays::stream)
    4. .forEach(System.out::println);

排序

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

    1. //过滤有效订单 根据用户id 进行排序
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .sorted((o1, o2) -> o1.getUserId() - o2.getUserId())
    4. .forEach(System.out::println);
    5. //或者等价写法
    6. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    7. .sorted(Comparator.comparingInt(Order::getUserId))
    8. .forEach(System.out::println);
  • 过滤有效订单 , 根据订单状态排序 如果订单状态相同根据订单创建时间排序

    1. //过滤有效订单 如果订单状态相同 根据订单创建时间排序 反之根据订单状态排序
    2. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    3. .sorted((o1, o2) -> {
    4. if (o1.getStatus().equals(o2.getStatus())) {
    5. return o1.getCreateDate().compareTo(o2.getCreateDate());
    6. } else {
    7. return o1.getStatus().compareTo(o2.getStatus());
    8. }})
    9. .forEach(System.out::println);
    10. // 等价形式
    11. ordersList.stream().filter((order) -> order.getIsValid() == 1)
    12. .sorted(Comparator.comparing(Order::getCreateDate)
    13. .thenComparing(Comparator.comparing(Order::getStatus)))
    14. .forEach(System.out::println);

来源丨https://juejin.im/post/6859293901497303047
作者丨同一片蓝天下