【Java 8】Stream中的Pipeline理解
基于下面一段代码:
public static void main(String[] args) {
List<String> list = Arrays.asList("123", "123123");
list.stream().map(item -> item+"").forEach(System.out::print);
}
stream()方法
显然,这里的list对象是一个ArrayList实例,debug代码进入stream方法,可以看见进入到Collection.java类中的stream()中

这里的源码如下:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
关于分割迭代器的内容会在另外一篇文章详解,这里不再赘述。进入StreamSupport.stream()方法:
StreamSupport.java
public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
Objects.requireNonNull(spliterator);
return new ReferencePipeline.Head<>(spliterator,
StreamOpFlag.fromCharacteristics(spliterator),
parallel);
}
咱们可以看到Stream是一个ReferencePipeline.Head类的实例,通过idea的类图结构功能,我们可以看到下面这个层次结构:

所有的流基本都是来自于BaseStream,AbstractPipeline,ReferencePipeline这三个抽象类或接口。
ReferencePipeline的实现类一共就三种:
- Head
- StatelessOp
- StatefulOp
查看了源码即可知道:AbstractPipeline其实就是一个双向链表中的一个节点。【我是这么理解的】
Head:代表的是流的源头,刚构建的时候流的一些属性被包含在这个对象。比如这个集合的元素,毕竟流的存在还是为了对一组元素的操作。
StatelessOp:代表的是无状态的操作,如map()
StatefulOp:代表的是有状态的操作,如sorted()

所以stream()方法执行之后,拿到的是一个ReferencePipeline.Head实例,并没有构建StatelessOp,StatefulOp实例。
map()方法
因为stream方法返回值是一个Head实例,而Head类并未重写map方法,所以map方法的实际执行还是走的ReferencePipeline类的map方法,如下:
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
这里的返回是一个继承于StatelessOp的匿名类。
关于Sink和TerminalOp的详解后续会单独开文章分析。这里只需要理解这个map的返回值是一个继承于StatelessOp的匿名类。(StatelessOp是一个ReferencePipeline的实现)
forEach()方法
前提:流是含有流源的对象,并且它支持0个或多个中间操作,1个终止操作的特性。
通过idea查看发现foreach的实现有2个:

第一个是Head的实现,因为流源构造出来之后,直接调用forEach,有它自己的实现,对迭代做了优化。这里可后续添加细致分析。
第二个是ReferencePipeline的实现,即调用终止操作的节点不是流源节点。
我们这里只分析ReferencePipeline中的实现:
public void forEach(Consumer<? super P_OUT> action) {
/**
* ForEachOps.makeRef(action, false) 是构建终止操作,参考3.1
* evaluate()是触发终止操作的调用,参考3.2
*/
evaluate(ForEachOps.makeRef(action, false));
}
这里的evaluate方法可以想象成“执行”的意思。
ForEachOps.makeRef(action, false)方法可以想象成“构造一个终止操作”。--终止操作是一个名词,这里只是一个对象而已,如果这个“操作”没有得到触发,那么流什么也不会干。所以这个evaluate可以理解成fire action performed.
构建终止操作
首先来看看TerminalOp接口,这是所有终止操作的抽象,每一个终止操作都是它的子类。

查看它的实现类,可以发现它的实现类的特点:
FindOp in FindOps
示例:findFirst()
ReduceOp in ReduceOps
示例:reduce(BigDecimal.Zero, BigDecimal::add)ForEachOp in ForEachOps
示例:forEach()MatchOp in MatchOps
示例:anyMatch()
其中带s的是一个工厂类,用于生产不同的“终止操作”。不带s的才是一个“终止操作”TerminalOp的实现类。
触发终止操作
其实这里也不是仅仅触发终止操作,这个方法里会把前面所有的中间操作apply到每一个元素上,并执行终止操作。
evaluate()的实现如下,暂时这里不做过多讨论,后续在sink的单独一篇文章中,分析具体流的执行过程。
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
assert getOutputShape() == terminalOp.inputShape();
if (linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
linkedOrConsumed = true;
return isParallel()
? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
: terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}
总结
本文只是为了理解:流pipeline是一个什么概念,以及它有什么样的基本特性?
1、流pipeline是一个双向链表的节点,前后引用。
2、流由流源,中间操作和终止操作组成。
3、终止操作被触发的时候,所有的操作(中间+终止)才会被一一应用到元素上。这称为流的惰性。
4、有一些操作是具有短路的特性的,如:findFirst等。
【Java 8】Stream中的Pipeline理解的更多相关文章
- Stream中的Pipeline理解
使用Stream已经快3年了,但是从未真正深入研究过Stream的底层实现. 今天开始把最近学到的Stream原理记录一下. 本篇文章简单描述一下自己对pipeline的理解. 基于下面一段代码: p ...
- java 8 stream中的Spliterator简介
目录 简介 tryAdvance trySplit estimateSize characteristics 举个例子 总结 java 8 stream中的Spliterator简介 简介 Split ...
- java 8 Stream中操作类型和peek的使用
目录 简介 中间操作和终止操作 peek 结论 java 8 Stream中操作类型和peek的使用 简介 java 8 stream作为流式操作有两种操作类型,中间操作和终止操作.这两种有什么区别呢 ...
- 对Java Web项目中路径的理解
第一个:文件分隔符 坑比Window.window分隔符 用\;unix采用/.于是用File.separator来跨平台 请注意:这是文件路径.在File f = new File(“c:\\hah ...
- java流stream中的collect()方法详解
public class StreamTest { /** * stream.collect() 的本质由三个参数构成, * 1. Supplier 生产者, 返回最终结果 * 2. BiConsum ...
- Stream中的Collector收集器原理
前言 Stream的基本操作因为平时工作中用得非常多(也能看到一些同事把Stream操作写得很丑陋),所以基本用法就不写文章记录了. 之所以能把Stream的操作写得很丑陋,完全是因为Stream底层 ...
- 【Java必修课】图说Stream中的skip()和limit()方法及组合使用
1 简介 本文将讲解Java 8 Stream中的两个方法:skip()和limit().这两个方法是Stream很常用的,不仅各自会被高频使用,还可以组合出现,并能实现一些小功能,如subList和 ...
- 【Java 8】Stream中flatMap方法
在java 8 Stream中,flatMap方法是一个维度升降的方法 举例说明 给 定 单 词 列 表["Hello","World"] ,要返回列表 [&q ...
- Java之Stream流
Stream流的初步学习 初次学习Stream流的学习笔记,学习之前先了解一下函数式接口 概述 API是一个程序向使用者提供的一些方法,通过这些方法就能实现某些功能.所以对于流API来 说,重点是怎么 ...
随机推荐
- 大一C语言学习笔记(5)---函数篇-定义函数需要了解注意的地方;定义函数的易错点;详细说明函数的每个组合部分的功能及注意事项
博主学习C语言是通过B站上的<郝斌C语言自学教程>,对于C语言初学者来说,我认为郝斌真的是在全网C语言学习课程中讲的最全面,到位的一个,这个不是真不是博主我吹他哈,大家可以去B站去看看,C ...
- c++学习笔记6(结构化程序设计)
结构化程序设计 c语言使用结构化程序设计: 程序=数据结构+算法 程序有全局变量以及众多相互调用的函数组成 算法以函数的形式实现,用于对数据结构进行操作 结构化程序设计不足
- 普通邮箱设置客户端授权码并开启stmp服务以及关于QQ邮箱“命令顺序不正确。 服务器响应为:Error: need EHLO and AUTH first !”问题全指导
Zoomla!逐浪CMS带有强大的邮局功能,可以用于发送邮件与进行事务管理. 其中邮局配置大家不太熟悉这里提供一系列教程. 1.首先在QQ邮箱当中开启"POP3/SMTP服务" 2 ...
- JSON实现序列化dump和dumps方法,JSON实现反序列化loads和load方法
通过文件操作,我们可以将字符串写入到一个本地文件.但是,如果是一个对象(例如列表.字典.元组等),就无 法直接写入到一个文件里,需要对这个对象进行序列化,然后才能写入到文件里. 设计一套协议,按照某种 ...
- CSS学习笔记:grid布局
目录 一.Grid布局简介 二.Grid布局的一些概念 三. 容器元素属性 1. grid-template-* 1.1 网格行和列的设置 1.2 repeat的使用 1.3 使用fr 1.4 aut ...
- 在 Kubernetes 上安装 Gitlab CI Runner Gitlab CI 基本概念以及 Runner 的安装
简介 从 Gitlab 8.0 开始,Gitlab CI 就已经集成在 Gitlab 中,我们只要在项目中添加一个.gitlab-ci.yml文件,然后添加一个Runner,即可进行持续集成.在介绍 ...
- Centos8 部署 ElasticSearch 集群并搭建 ELK,基于Logstash同步MySQL数据到ElasticSearch
Centos8安装Docker 1.更新一下yum [root@VM-24-9-centos ~]# yum -y update 2.安装containerd.io # centos8默认使用podm ...
- [bzoj3670]动物园
首先计算出s数组,s表示可以重复的前缀等于后缀的个数,显然有s[i]=s[next[i]]+1,因为有且仅有next的next满足这个条件. 然后直接暴力枚举所有next,直到它小于i的一半,这个时间 ...
- [atARC101E]Ribbons on Tree
令$f(E')$表示强制$E'$中的边不被覆盖的方案数,根据容斥,$ans=\sum_{E'\subseteq E}(-1)^{|E'|}f(E')$ 对于给定的$E'$,$f(E')$即将$E'$中 ...
- myeclipse maven web打包
1.在当前的项目pom.xml的文件上,如下图所示:鼠标右键->run As->Maven Build...