图文分解 Java8 Stream 源码的奥秘

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

核心回顾

stream 是一次性的,不是数据结构,不存储数据,不改变源数据.。

API 分为终端和中间操作,中间操作是惰性的,碰到终端才去执行。

中间操作有无状态和有状态之分,有状态需要更改上一步操作获得的所有元素,才可以进行下一步操作,比如 排序 sorted,去重 distinct,跳过 skip,限制 limit 这四个,需要多迭代一次。

终端操作有短路与否之分,短路操作有 anyMatch, allMatch, noneMatch, findFirst, findAny

整体概览

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

源码中涉及到 N 多的内部类,这个是删减后的版本

比如 :

demo代码

这里沿用上文的例子

        Student aStud = new Student(1, "a");
        Student bStud = new Student(2, "b");
        Student cStud = new Student(3, null);

//         集合的创建 一
        List collect1 = Stream.of(aStud, bStud, cStud).collect(Collectors.toList());
        collect1.forEach(System.out::println);

        List studNameList = studentList.stream()
                .map(Student::getName)
                .filter(Objects::nonNull)
                .map(String::toUpperCase)
                .sorted()
                .map(e -> e + "c")
                .collect(Collectors.toList());

步骤解析

都在这里了

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

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

wrapSink()

开始套娃,从 ReducingSink 往前套

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

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

Sink 链创建结果

copyInto()

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

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

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

filter 链节点,多一步判断

sorted 节点,添加到 list 中。

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

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

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

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

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

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

总结

代码对应的执行流程

  1. 先创建流,出现了 Head 节点
  2. 创建中间管道 Pipeline
  3. 调用终端操作后有三步 (一)将中间管道的 Sink 操作连接在一起 (wrapSink) (二)处理数据 (copyInto),主要调用 Sink 中的 begin,accept(核心),end 操作 (三)返回结果,ReducingSink 中的 get 方法

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

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

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

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

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

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

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

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

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

工厂模式

对 stream 的特点更加熟悉

比如:

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

最后

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

展开阅读全文

页面更新:2024-04-29

标签:源码   数据结构   终端   节点   奥秘   分解   状态   核心   模式   操作   图文   方法   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top