通过分析创建Stream的过程,详细介绍了Spliterator接口定义,Spliterator子类的实现细节,Spliterator在Stream中的调用时机,以及代表源阶段Stream的Head类结构。本章将继续带着大家深入理解什么是Stream中间操作,进入每一个中间操作的源码了解我们定义的lambda表达式是如何在流上处理数据的。
中间操作
Stream是惰性流,中间操作只是将lambda表达式记录下来,返回一个新的Stream,只有终止操作被调用时,才会触发计算。这样可以保证数据被尽量少的遍历,这也是Stream高效的原因之一。中间操作分为无状态操作和有状态操作,无状态操作不会存储元素的中间状态,有状态操作会保存元素中间状态。
我看到有这样的说法:无状态操作是指元素的处理不受之前元素的影响;有状态操作是指该操作只有拿到所有元素之后才能继续下去。实际上真的是这样吗?我们后面会通过分析源码寻找答案。现在先来看看中间操作的划分吧:
操作分类 | 方法 |
---|---|
无状态操作 | filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek() unordered() |
有状态操作 | distinct() sorted() limit() skip() |
无状态操作
在 Java8 Stream源码精讲(一):从一个简单的例子入手 中提到,map()和filter()中间操作被调用之后,返回的是一个StatelessOp匿名子类的实例。通过类继承结构可以看到,它跟Head一样都是继承ReferencePipeline,不同的是它是一个抽象类,所以具体的逻辑还是放在子类中的。
实际上StatelessOp就代表无状态中间操作,它将操作声明的lambda函数保存在某个地方,在适当的时机调用。
abstract static class StatelessOp<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> { //传入上一个阶段的Stream,构建双向链表 StatelessOp(AbstractPipeline<?, E_IN, ?> upstream, StreamShape inputShape, int opFlags) { super(upstream, opFlags); assert upstream.getOutputShape() == inputShape; } //标识当前是一个无状态操作 @Override final boolean opIsStateful() { return false; } } 复制代码
可以看到StatelessOp并没有多少内容,只是可以通过构造器与上一个Stream连接成一个链表,然后实现了opIsStateful()方法。还得看一下它的父类的父类AbstractPipeline的构造函数:
AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) { if (previousStage.linkedOrConsumed) throw new IllegalStateException(MSG_STREAM_LINKED); previousStage.linkedOrConsumed = true; //构建双向链表的过程 previousStage.nextStage = this; this.previousStage = previousStage; this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK; this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags); this.sourceStage = previousStage.sourceStage; //每一次中间操作,如果是有状态的,那么sourceStage的sourceAnyStateful会被标记为true if (opIsStateful()) sourceStage.sourceAnyStateful = true; this.depth = previousStage.depth + 1; } 复制代码
下面我们来看看每一个中间操作方法有哪些异同吧。
filter()方法
filter()方法大家都很熟悉了,返回一个匹配predicate函数的元素组成的Stream。
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) { Objects.requireNonNull(predicate); //把当前阶段的Stream传入,将新返回的StatelessOp加入链表末尾 return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE, StreamOpFlag.NOT_SIZED) { @Override Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) { return new Sink.ChainedReference<P_OUT, P_OUT>(sink) { @Override public void begin(long size) { downstream.begin(-1); } //predicate不会被立即被调用,会在恰当的时机触发 @Override public void accept(P_OUT u) { if (predicate.test(u)) downstream.accept(u); } }; } }; } 复制代码
跟上面分析的一样,调用filter()会返回一个新的StatelessOp,与上一个Stream组成链表,lambda表达式不会被马上调用,只是保存在内部。 它被调用的地方是Sink.ChainedReference的accept()方法。
这里大家可能被绕晕,Sink是什么?ChainedReference又是什么,它内部的downstream又是什么?
Sink接口扩展自Consumer,用于在流管道的各个阶段传递元素,并使用其begin()、end()和cancellationRequested()方法来管理大小信息、控制流。
interface Sink<T> extends Consumer<T> { default void begin(long size) {} default void end() {} default boolean cancellationRequested() { return false; } } 复制代码
- begin()方法:在Stream中的元素传递给sink之前会调用这个方法,对于有状态操作,通常会做一些初始化工作,参数是元素大小,如果大小不确定,传递-1。
- accept()方法:Stream中的每一个元素都会经过accept()方法进行逻辑处理。
- end()方法:当所有元素都被sink处理了,会调用这个方法表示结束,对于有状态操作,会做清理工作