当前位置 : 主页 > 编程语言 > java >

深入理解Stream之原理剖析

来源:互联网 收集:自由互联 发布时间:2022-09-02
大家好,我是李哥。 JDK1.6和JDK1.8是主流的两个大版本,目前市场上用的最多最多的依然是JDK1.8。所以,我们有必要聊一聊Java8的一些新特性。 深入理解lambda的奥秘 深入理解Stream之原理

大家好,我是李哥。


JDK1.6和JDK1.8是主流的两个大版本,目前市场上用的最多最多的依然是JDK1.8。所以,我们有必要聊一聊Java8的一些新特性。


  • 深入理解lambda的奥秘
  • 深入理解Stream之原理剖析
  • 深入理解Stream之foreach源码解析
  • 深入浅出NPE神器Optional
  • 谈谈接口默认方法与静态方法
  • 深入浅出重复注解与类型注解
  • 深入浅出JVM元空间metaspace
  • 深入理解CompletableFuture异步编程

  • 今天我们先来聊聊深入理解Stream之原理剖析。


    深入理解Stream之原理剖析_java


    Stream操作分类


    深入理解Stream之原理剖析_java_02


    Stream中的操作可以分为两大类:中间操作与结束操作。


    中间操作只会进行操作记录,只有结束操作才会触发实际的计算,可以理解为懒加载,这也是Stream在操作大对象迭代计算的时候如此高效的原因之一。


    中间操作分为有状态操作与无状态操作,无状态是指元素的处理不受之前元素的影响,有状态是指该操作只有拿到所有元素之后才能继续下去。这也比较好理解,比如有状态的distinct()去重方法,你说他能不关心其他值吗?当然不能,他必须拿到所有元素才知道当前迭代的元素是否被重复。


    结束操作可以分为短路与非短路操作,这个应该很好理解,短路是指遇到某些符合条件的元素就可以得到最终结果;而非短路是指必须处理所有元素才能得到最终结果。


    之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。


    Stream结构分析


    让我们先简单看看下面一段代码:


    List<String> list = new ArrayList<>();

    // 获取stream1
    Stream<String> stream1 = list.stream();

    // stream1通过filter后得到stream2
    Stream<String> stream2 = stream1.filter("lige"::equals);

    // stream1与stream2是同一个对象吗?
    System.out.println("stream1.equals(stream2) = " + stream1.equals(stream2));
    System.out.println("stream1.classTypeName = " + stream1.getClass().getTypeName());
    System.out.println("stream2.classTypeName = " + stream2.getClass().getTypeName());

    // 结果
    // stream1.equals(stream2) = false
    // stream1.classTypeName = java.util.stream.ReferencePipeline$Head
    // stream1.classTypeName = java.util.stream.ReferencePipeline$2


    很明显,stream1与stream2不是同一个对象,并且他们不是同一个实现类。stream1的实现类为ReferencePipeline$Head,而stream2的实现类为一个匿名内部类,让我们进步一分析其源码,所谓源码之下,无所遁形。


    深入理解Stream之原理剖析_java_03


    深入理解Stream之原理剖析_Stream_04


    深入理解Stream之原理剖析_javaee_05


    让我们再看看stream2:


    深入理解Stream之原理剖析_java_06


    深入理解Stream之原理剖析_javaee_07


    深入理解Stream之原理剖析_java_08


    深入理解Stream之原理剖析_javaee_09


    深入理解Stream之原理剖析_java_10


    深入理解Stream之原理剖析_javaee_11


    通过分析我们可以发现,stream2的实现类是StatelessOp,所以就形成了这样一个结构。


    深入理解Stream之原理剖析_Stream_12


    每一次中间操作都会生成一个新的Stream,如果是无状态操作则实现类是StatelessOp,如果是有状态操作则实现类是StatefulOp。


    让我们再来看一下他们之间的继承关系。


    深入理解Stream之原理剖析_Stream_13


    深入理解Stream之原理剖析_源码分析_14


    再聊核心Sink


    实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接口方法。


    我还是从一个示例开始:


    List<String> list = new ArrayList<>();
    list.add("zhangsan");
    list.add("ligeligeligeligeligeligeligeligeligelige");
    list.add("lisilisilisilisilisilisilisilisi");
    list.add("wangwu");
    list.add("ligejishuligejishuligejishuligejishuligejishuligejishuligejishu");

    List<String> resultList = list.stream()
    .filter(it -> it.contains("li"))// 1. 只要包含li的数据
    .filter(it -> it.contains("lige"))// 2. 只要包含lige的数据
    .map(String::toUpperCase)// 3. 对符合的数据作进一步加工,转换大写
    .map(String::toLowerCase)// 4. 对符合的数据作进一步加工,转换小写
    .collect(Collectors.toList());

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


    不管是filter方法,还是map方法,还是其他的方法,我们进入到源码层面,返回了一个StatelessOp对象或StatefulOp对象。


    所以便产生了这样一个结构:


    深入理解Stream之原理剖析_源码分析_15


    但是和Sink有什么关系呢?我们再反过来看filter或者map源码:


    深入理解Stream之原理剖析_javaee_16


    直接返回一个匿名StatelessOp对象,实现opWrapSink方法,opWrapSink方法是传入一个sink对象,返回另一个sink对象。而新的sink对象拥有传入sink对象的引用。


    但是,这个代码有什么用?什么时候触发的呢?


    别着急,让我们从collect(Collectors.toList())方法开始一步一步深入研究。


    深入理解Stream之原理剖析_Stream_17


    深入理解Stream之原理剖析_Stream_18


    深入理解Stream之原理剖析_javaee_19


    这里我们需要知道传入xx方法的终端对象是ReduceOp,并且这个ReduceOp对象在makeSink的时候返回了一个匿名内部类ReducingSink对象。


    深入理解Stream之原理剖析_javaee_20


    深入理解Stream之原理剖析_javaee_21


    深入理解Stream之原理剖析_java_22


    这里的makeSink我们提到过,返回一个匿名内部类ReducingSink对象。


    深入理解Stream之原理剖析_javaee_23


    先执行warpSink,再执行copyInto。直白一点就是先对Sink进行包装成链式Sink,再遍历Sink链进行copy到结果对象里。这里的两个步骤都很核心。


    先看warpSink:


    深入理解Stream之原理剖析_源码分析_24


  • 首次进入时,this为最后的Stream对象,从尾部向头部遍历
  • 每次遍历时,得到一个新的Stream对象,一般为StatelessOp对象或StatefulOp对象
  • 执行操作对象的opWrapSink方法,这就是匿名实现了。
  • 在每一个opWrapSink实现方法中,传入了上一个sink,最终得到一个sink链表

  • 深入理解Stream之原理剖析_源码分析_25


    最后,返回Sink链的头节点,内部称之为包装好的sink,命名wrapped,随后,准备进行执行begin,forEachRemaining,end方法。


    深入理解Stream之原理剖析_javaee_26


    forEachRemaning最终调用accept方法。


    深入理解Stream之原理剖析_Stream_27


    动画理解Stream执行流程


    深入理解Stream之原理剖析_javaee_28


    深入理解Stream之原理剖析_java_29

    上一篇:elasticsearch+springboot实现客户端搜索
    下一篇:没有了
    网友评论