likes
comments
collection
share

Java8 Stream 的核心秘密

作者站长头像
站长
· 阅读数 22

本文正在参加「金石计划 . 瓜分6万现金大奖」

小伙伴们好呀,我是 4ye,今天来分享下 Java8 Stream 的源码

Java8 Stream 的核心秘密

核心回顾

  1. stream 是一次性的,不是数据结构,不存储数据,不改变源数据.。
  2. API 分为终端和中间操作,中间操作是惰性的,碰到终端才去执行。
  3. 中间操作有无状态和有状态之分,有状态需要更改上一步操作获得的所有元素,才可以进行下一步操作,比如 排序 sorted,去重 distinct,跳过 skip,限制 limit 这四个,需要多迭代一次。
  4. 终端操作有短路与否之分,短路操作有 anyMatch, allMatch, noneMatch, findFirst, findAny

整体概览

这里列出一些重要的类,是看源码过程中必须了解的。

Java8 Stream 的核心秘密

比如 :

  • Sink 接口是数据实际操作的地方,核心方法: begin,accept,end
  • AbstractPipeline 抽象类是核心的数据结构,双链表

demo代码

这里沿用上文的例子

        Student aStud = new Student(1, "a");
        Student bStud = new Student(2, "b");
        Student cStud = new Student(3, null);
​
//         集合的创建 一
        List<Student> collect1 = Stream.of(aStud, bStud, cStud).collect(Collectors.toList());
        collect1.forEach(System.out::println);
​
        List<String> studNameList = studentList.stream()
                .map(Student::getName)
                .filter(Objects::nonNull)
                .map(String::toUpperCase)
                .sorted()
                .map(e -> e + "c")
                .collect(Collectors.toList());

步骤解析

都在这里了 👇

Java8 Stream 的核心秘密

这里步骤太多了,就不一一放出来了 ,列下核心

  1. wrapSink, 创建 Sink 链,将管道的 Sink 操作连接在一起
  2. copyInto , 处理数据

wrapSink()

开始套娃,从 ReducingSink 往前套

Java8 Stream 的核心秘密

opWarpSink 方法调用的是每一步 中间操作 中的方法

Java8 Stream 的核心秘密

通过 单链表 的形式将他们联系在一起

Java8 Stream 的核心秘密

Sink 链创建结果

Java8 Stream 的核心秘密

copyInto()

这里判断是不是 短路操作 ,然后就去执行 Sink 的 begin,accept,end 方法。

通过 forEachRemaining 进行内部迭代,这个是 Spliterator 的方法。

Java8 Stream 的核心秘密

map 链节点,直接调用传进来的方法,

Java8 Stream 的核心秘密

filter 链节点,多一步判断

Java8 Stream 的核心秘密

sorted 节点,添加到 list 中。

注意,此时没有继续调用 downstream.accept 方法!

意味着,我们代码中的 5 个中间步骤只执行了前 3 个。

Java8 Stream 的核心秘密

不过别担心, sorted 链节点中它重写了这个 end,并开启对新数据的新一轮遍历

这就是我们提到的,有状态中间操作多一次迭代的原因

Java8 Stream 的核心秘密

最后呢,是来到终止操作 TerminalOp 中的 accept,这里执行的是 list 的 add 方法(我们调用 Collectors.toList() 中构建的),至此,数据添加到 state 中

Java8 Stream 的核心秘密

Java8 Stream 的核心秘密

获取数据,ReducingSink 继承了 Box 这个抽象类,最后 get 方法得到结果。

Java8 Stream 的核心秘密

总结

代码对应的执行流程👇

  1. 先创建流,出现了 Head 节点

  2. 创建中间管道 Pipeline

  3. 调用终端操作后有三步 👇

    (一)将中间管道的 Sink 操作连接在一起 (wrapSink) (二)处理数据 (copyInto),主要调用 Sink 中的 begin,accept(核心),end 操作 (三)返回结果,ReducingSink 中的 get 方法

Java8 Stream 的核心秘密

主要记住这个 wrapSink 方法 和 copyInto 方法。

一个套娃,一个调用 begin,accept,end 等方法。

那么,这个 stream 的原理机制就出来了:

利用 wrapSink 方法将各个中间操作中的 Sink 嵌套在一起,然后来到 copyInto 方法,调用 begin 通知各个 Sink 做好准备,接着进行内部迭代调用 accept 方法,再调用 end 方法完成数据的操作,最后通过 get 方法,获取新容器中的数据,便是结果了。

此外,源码的 链式调用API 写法设计模式 的使用以及 泛型四大函数式接口 组合构建的高度抽象,封装写法,对我们的编码能力,源码阅读能力也有很大的帮助!

比如 这个 Consumer+Function 接口的组合,配合泛型上下限的使用

Java8 Stream 的核心秘密

源码中 访问者模式工厂模式 等设计模式的影子

访问者模式: 将数据结构与数据操作分离

对应源码:数据结构是 Pipeline ,操作是 Sink

Java8 Stream 的核心秘密

对 stream 的特点更加熟悉

比如:

  1. stream 是一次性的,不是数据结构,不存储数据,不改变源数据.。
  2. 中间操作是惰性的,遇到 终端操作才真正执行
  3. 有状态的中间操作的特殊之处在于多迭代一次
  4. 内部迭代
  5. 终端操作主要做了两件事,串连中间操作,调用 accept 方法处理数据

当然这里我也只测试了 非短路模式,短路模式又是怎样设计的呢,小伙伴们可以自己上手 debug 下,加深印象。

最后

有所收获的话,别忘了 三连(点赞,在看,转发) 鼓励下 4ye 呀,谢谢&下期见!😝