Java8 Stream源码精讲(四):一文说透四种终止操作

简介

Java8 Stream源码精讲(一):从一个简单的例子入手
Java8 Stream源码精讲(二):Stream创建原理深度解析
Java8 Stream源码精讲(三):中间操作原理详解

上一篇文章中,我们讲了Stream中间操作,通过分析源码的方式了解了无状态操作和有状态操作的区别,每一个中间操作方法是如何实现的,Stream是惰性流,调用中间操作比如filter()、map()等方法不会立即执行声明的lambda表达式,只有通过调用终止操作才会处理Stream中的元素。本章我们将分析终止操作相关源码,深入了解内部原理。

终止操作

在Stream API中有一个接口TerminalOp代表终止操作。

interface TerminalOp<E_IN, R> {
default StreamShape inputShape() { return StreamShape.REFERENCE; }

default int getOpFlags() { return 0; }

default &lt;P_IN&gt; R evaluateParallel(PipelineHelper&lt;E_IN&gt; helper,
                                  Spliterator&lt;P_IN&gt; spliterator) {
    if (Tripwire.ENABLED)
        Tripwire.trip(getClass(), "{0} triggering TerminalOp.evaluateParallel serial default");
    return evaluateSequential(helper, spliterator);
}

&lt;P_IN&gt; R evaluateSequential(PipelineHelper&lt;E_IN&gt; helper,
                            Spliterator&lt;P_IN&gt; spliterator);

}
复制代码

接口上定义了两个泛型类型:

  • E_IN:输入的元素类型
  • R:结果类型

来看下方法定义:

  • inputShape():获取操作的输入元素类型,返回枚举StreamShape,译为Stream的形状。可以看到定义了四种枚举,正好对应Stream、IntStream、LongStream、DoubleStream。
enum StreamShape {
REFERENCE,

INT_VALUE,

LONG_VALUE,

DOUBLE_VALUE

}
复制代码

  • evaluateParallel():Stream是并行时,终止操作最终调用这个方法处理数据,我们不关心。
  • evaluateSequential():串行流时最终调用这个方法处理数据。

64H2J@AUONS%{WM7LWM0M.png

通过继承关系可以看到,只有四个类直接实现TerminalOp接口,Stream有这么多终止方法,而实际上可以归类为4个,单独说明一下:toArray()方法也是终止操作,但是与其它不同,没有使用TerminalOp。

现在来看下终止操作划分:

操作类型方法
非短路操作forEach() forEachOrdered() toArray() reduce() collect() min() max() count()
短路操作anyMatch() allMatch() noneMatch() findFirst() findAny()

非短路操作

非短路操作主要有ForEachOp和ReduceOp两个TerminalOp实现,其实toArray()也属于非短路操作,但是它没有依靠实现TerminalOp来完成相应的功能,所以不作讲解,感兴趣的小伙伴可以自己看一下相关源码。

ForEachOp

用于遍历Stream元素的forEach()和forEachOrdered()方法都是通过ForEachOp的子类完成相应工作的。

ForEachOp.png ForEachOp的继承结构比较复杂,除了实现上面的TerminalOp外,还实现了TerminalSink。TerminalSink聚合了Sink和Supplier接口,这两个大家应该都不陌生,Sink在前面的文章有详细讲解,Supplier是一个函数式接口,用于提供一个结果给调用者。

通过前面的文章和继承结构,我们可以大胆猜测:ForEachOp除了具备终止操作的能力,在数据处理之前,自己还会作为一个sink,与中间操作中的sink实例组成sink链表,通过责任链模式依次处理Stream中的元素。到底是不是这样呢?我们进入源码求证。

//省略了并行处理相关的代码
//泛型声明:T表示输入元素类型;Void表示返回结果类型,forEach()没有返回值
static abstract class ForEachOp<T>
        implements TerminalOp<T, Void>, TerminalSink<T, Void> {
    //odered表示是否按元素顺序遍历,因为并行处理可能不是按Stream中元素顺序遍历,不用过多关注
    private final boolean ordered;
protected ForEachOp(boolean ordered) {
    this.ordered = ordered;
}

// TerminalOp

@Override
public int getOpFlags() {
    return ordered ? 0 : StreamOpFlag.NOT_ORDERED;
}

@Override
public &lt;S&gt; Void evaluateSequential(PipelineHelper&lt;T&gt; helper,
                                   Spliterator&lt;S&gt; spliterator) {
    return helper.wrapAndCopyInto(this, spliterator).get();
}

// TerminalSink

@Override
public Void get() {
    return null;
}

}
复制代码

重点关注evaluateSequential(),内部会调用PipelineHelper#wrapAndCopyInto()方法,看到这个方法是不是很熟悉,没错就是前面文章中多次提到的处理数据的方法,它的职责是将传入sink与中间操作产生的sink组合成链表,然后调用源Spliterator的方法,发送Stream元素给sink链处理。

我们进入这个方法看一下,它是在AbstractPipeline中实现的:

final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
    copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);
    return sink;
}
复制代码

可以看到首先调用wrapSink()方法,将封装有终止操作逻辑的sink再次包装:

final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    Objects.requireNonNull(sink);
for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth &gt; 0; p=p.previousStage) {
    sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
}
return (Sink&lt;P_IN&gt;) sink;

}
复制代码

wrapSink()方法前面也有多次讲到,Stream经过一系列中间操作调用,返回的实际上是一个Stream链表,结构如下:

4_2B$YFV`)Y6(0Q4HJVZ9.png

这里的逻辑就是从后向前遍历这个链表,调用每一个节点上的AbstractPipeline#opWrapSink()方法,将除开Head节点之外,每一个中间操作当中的sink,与后一个中间操作的sink节点连接,最后一个节点就是代表终止操作的sink。

6MP_BP0}JP@_JDXX4(ISH)V.png

拿到sink链表之后,再来看看copyInto()方法是如何执行数据处理逻辑的:

final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    Objects.requireNonNull(wrappedSink);
//非短路操作
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
    //1.begin()方法调用
    wrappedSink.begin(spliterator.getExactSizeIfKnown());
    //2.forEachRemaining()方法调用,发送元素
    spliterator.forEachRemaining(wrappedSink);
    //3.end()方法调用,通知结束
    wrappedSink.end();
}
//短路操作
else {
    copyIntoWithCancel(wrappedSink, spliterator);
}

}
复制代码

因为ForEachOp是非短路操作,所以必定走第一个分支,现在只分析这个逻辑:

  1. 在发送元素之前,调用begin()方法,将元素大小传入sink链表,经过有状态中间操作时会初始化相应的变量,这在前一章有详细分析,最终调用终止操作sink。
  2. 调用Spliterator#forEachRemaining()方法遍历元素,内部会调用Sink#accept()方法传入每一个元素,同样元素会在sink链表上经过中间操作处理,最后到达终止操作。
  3. end()方法,同理通知结束,做清理工作。

forEach()方法

forEach()方法,调用action函数处理Stream上传递过来的每一个元素,注意在并行流中,元素实际被处理的顺序可能不是Stream的元素顺序。ReferencePipelinet#forEach():

public void forEach(Consumer<? super P_OUT> action) {
    evaluate(ForEachOps.makeRef(action, false));
}
复制代码

ForEachOps#makeRef()方法:

public static <T> TerminalOp<T, Void> makeRef(Consumer<? super T> action,
                                              boolean ordered) {
    Objects.requireNonNull(action);
    return new ForEachOp.OfRef<>(action, ordered);
}
复制代码

创建并返回OfRef对象,可以看到它继承自ForEachOp,实现了accept()方法,也就是元素到达终止操作sink时,会调用lambda表达式。

static final class OfRef<T> extends ForEachOp<T> {
    final Consumer<? super T> consumer;
OfRef(Consumer&lt;? super T&gt; consumer, boolean ordered) {
    super(ordered);
    this.consumer = consumer;
}

@Override
public void accept(T t) {
    consumer.accept(t);
}

}
复制代码

最后再来看一下AbstractPipeline#evaluate(),每一个终止操作方法内部都会调用evaluate()方法触发Stream上的逻辑执行,在它的内部,又会调用TerminalOp#evaluateSequential()方法:

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()));

}
复制代码

所以通过前面的分析,forEach()方法的流程应该很清晰了:先创建继承自ForEachOp的OfRef对象,然后通过调用AbstractPipeline#evaluate()方法,间接调用ForEachOp#evaluateSequential()方法,将OfRef作为终止操作的sink与中间操作的sink构建成链表,最后通过调用Spliterator#forEachRemaining()方法遍历元素将元素传递给sink链, 而代表forEach()终止操作sink的OfRef对象每接收到上一个sink传递过来的元素都会调用声明的lambda表达式进行处理。

forEachOrdered()方法

forEachOrdered()方法,调用action函数处理Stream上传递过来的每一个元素,在并行流中也能保证元素处理顺序和Stream一致。在串行流中行为和forEach()表现一致。

public void forEachOrdered(Consumer<? super P_OUT> action) {
    evaluate(ForEachOps.makeRef(action, true));
}
复制代码

ReduceOp

ReduceOp是用于聚合Stream中元素的终止操作,ReduceOp应该是工作中被开发者使用最多的终止操作,比如:reduce()、collect()、max()等等。

//省略了并行处理的方法
private static abstract class ReduceOp<T, R, S extends AccumulatingSink<T, R, S>>
        implements TerminalOp<T, R> {
    private final StreamShape inputShape;
ReduceOp(StreamShape shape) {
    inputShape = shape;
}

public abstract S makeSink();

@Override
public StreamShape inputShape() {
    return inputShape;
}

@Override
public &lt;P_IN&gt; R evaluateSequential(PipelineHelper&lt;T&gt; helper,
                                   Spliterator&lt;P_IN&gt; spliterator) {
    return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}

}
复制代码

可以看到ReduceOp继承关系比ForEachOp更简单,ReduceOp没有实现TerminalSink接口,所以它自身不包含终止操作的逻辑,是通过调用重写的makeSink()方法拿到TerminalSink,再传入AbstractPipeline#wrapAndCopyInto()进行处理的,具体的逻辑还得在各个终止操作方法中查看。

reduce()方法

reduce()有三个重载方法,我们选择两个重点分析:

  1. reduce(final P_OUT identity, final BinaryOperator<P_OUT> accumulator):
public final P_OUT reduce(final P_OUT identity, final BinaryOperator<P_OUT> accumulator) {
    return evaluate(ReduceOps.makeRef(identity, accumulator, accumulator));
}
复制代码

ReduceOps#makeRef():

public static <T, U> TerminalOp<T, U>
makeRef(U seed, BiFunction<U, ? super T, U> reducer, BinaryOperator<U> combiner) {
    Objects.requireNonNull(reducer);
    Objects.requireNonNull(combiner);
    //实现了AccumulatingSink接口,其实AccumulatingSink接口继承自TerminalSink
    //然后扩展了一个方法combine(),这个方法是用于并行计算的,不用关心
    //ReducingSink还继承了Box,Box中只保存了一个state初始变量,
    //用于与Stream中的每一个元素进行聚合计算
    class ReducingSink extends Box<U> implements AccumulatingSink<T, U, ReducingSink> {
        @Override
        public void begin(long size) {
            //初始变量state,这里赋值的就是Stream#reduce()方法传入的identity
            state = seed;
        }
    @Override
    public void accept(T t) {
        //应用reducer函数将元素和state计算,产生一个新的结果并覆盖之前的结果
        state = reducer.apply(state, t);
    }
    
    //用于并行计算
    @Override
    public void combine(ReducingSink other) {
        state = combiner.apply(state, other.state);
    }
}
return new ReduceOp&lt;T, U, ReducingSink&gt;(StreamShape.REFERENCE) {
    @Override
    public ReducingSink makeSink() {
        //创建上面的内部类实例
        return new ReducingSink();
    }
};

}
复制代码

reduce()方法确实是创建了一个 ReduceOp实例,它利用ReducingSink来保存初始结果,并且与每一个元素计算之后覆盖之前的结果。最后通过ReducingSink#get()方法作为Stream终止操作的结果返回。

  1. reduce(BinaryOperator<P_OUT> accumulator):
public final Optional<P_OUT> reduce(BinaryOperator<P_OUT> accumulator) {
    return evaluate(ReduceOps.makeRef(accumulator));
}
复制代码

ReduceOps#makeRef():

public static <T> TerminalOp<T, Optional<T>>
makeRef(BinaryOperator<T> operator) {
    Objects.requireNonNull(operator);
    class ReducingSink
            implements AccumulatingSink<T, Optional<T>, ReducingSink> {
        //sink初始化时为空,也就是没有计算结果
        private boolean empty;
        //保存计算结果
        private T state;
    public void begin(long size) {
        //初始状态
        empty = true;
        state = null;
    }

    @Override
    public void accept(T t) {
        if (empty) {
            //Stream中的第一个元素到来,直接作为结果保存到state
            empty = false;
            state = t;
        } else {
            //之后的元素,都应用operator函数与之前的结果计算,产生一个新的结果
            state = operator.apply(state, t);
        }
    }

    @Override
    public Optional&lt;T&gt; get() {
        //返回结果的方法,如果Stream中没有元素到达终止操作,则不存在结果,所以返回Optional
        return empty ? Optional.empty() : Optional.of(state);
    }

    @Override
    public void combine(ReducingSink other) {
        if (!other.empty)
            accept(other.state);
    }
}
return new ReduceOp&lt;T, Optional&lt;T&gt;, ReducingSink&gt;(StreamShape.REFERENCE) {
    @Override
    public ReducingSink makeSink() {
        return new ReducingSink();
    }
};

}
复制代码

可以看到,与上面的reduce()方法不同的是,它没有一个初始结果,所以在ReducingSink中,使用了两个变量来保存状态,第一个元素作为初始结果与后面的元素计算,并且方法声明的返回值是Optional类型,代表这个操作可能是没有结果的。

max()方法

max()方法,使用比较器比较元素中最大的值并返回。max()和min()方法都是对reduce()方法的特殊应用。

public final Optional<P_OUT> max(Comparator<? super P_OUT> comparator) {
    return reduce(BinaryOperator.maxBy(comparator));
}
复制代码

max()方法调用了reduce()方法,我们看下BinaryOperator#maxBy()返回什么:

public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
    Objects.requireNonNull(comparator);
    return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
复制代码

逻辑很简单,就是利用比较器比较当前元素和前面所有元素的较大值,并作为最终结果返回。

min()方法

min()方法,使用比较器比较元素中最小的值并返回。逻辑跟max()方法基本完全一样。

public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
    Objects.requireNonNull(comparator);
    return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
复制代码

collect()方法

上面说ReduceOp是工作中用到的最多的终止操作,那collect()方法应该就是ReduceOp操作中使用最频繁的终止操作方法了。collect()也有两个重载方法,我们重点看带Collector参数的方法。

Collector中文意思是收集器,它提供了获取Supplier、BiConsumer和BinaryOperator等函数的方法,Collector常用在分组、规约、聚合等操作当中。关于Collector这个强大的收集器,我打算放到下一章详细讲解,本章我们先看下collect()是如何使用ReduceOp的。

public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
    A container;
    //并行处理,不关注
    if (isParallel()
            && (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
            && (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
        container = collector.supplier().get();
        BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
        forEach(u -> accumulator.accept(container, u));
    }
    else {
        //重点在这里
        container = evaluate(ReduceOps.makeRef(collector));
    }
    return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
           ? (R) container
           : collector.finisher().apply(container);
}
复制代码

进入ReduceOps#makeRef()方法:

public static <T, I> TerminalOp<T, I>
makeRef(Collector<? super T, I, ?> collector) {
    //提供结果的Supplier函数
    Supplier<I> supplier = Objects.requireNonNull(collector).supplier();】
    //用于计算累加结果的函数
    BiConsumer<I, ? super T> accumulator = collector.accumulator();
    BinaryOperator<I> combiner = collector.combiner();
    class ReducingSink extends Box<I>
            implements AccumulatingSink<T, I, ReducingSink> {
        @Override
        public void begin(long size) {
            //获取和初始化结果
            state = supplier.get();
        }
    @Override
    public void accept(T t) {
        //应用accumulator函数计算结果
        accumulator.accept(state, t);
    }

    @Override
    public void combine(ReducingSink other) {
        state = combiner.apply(state, other.state);
    }
}
return new ReduceOp&lt;T, I, ReducingSink&gt;(StreamShape.REFERENCE) {
    @Override
    public ReducingSink makeSink() {
        return new ReducingSink();
    }

    @Override
    public int getOpFlags() {
        return collector.characteristics().contains(Collector.Characteristics.UNORDERED)
               ? StreamOpFlag.NOT_ORDERED
               : 0;
    }
};

}
复制代码

可以看到跟reduce()方法一样,collect()也是利用ReduceOp来实现的,不过它返回的ReducingSink有些细微的差别,collect()中保持结果的state是由Collector中的supplier提供的,并且处理Stream元素时不会覆盖之前的结果,而是使用Collector#accumulator()方法提供的BiConsumer函数修改state的内部数据。

Stream API通过提供Collector接口,使开发者可以更灵活的处理数据,甚至可以自定义Collector实现类以满足功能。

短路操作

短路操作跟非短路操作的主要区别在于:如果是短路操作,那么不一定需要完整的遍历整个Stream的元素,在某些条件下,可以提前得到结果,提前结束遍历过程。短路操作有MatchOp和FindOp两个TerminalOp实现,分别表示匹配和查找。

MatchOp

MatchOp是一种通过Predicate函数与元素匹配的终止操作,匹配类型包含:ALL、ANY和NONE。

private static final class MatchOp<T> implements TerminalOp<T, Boolean> {
    private final StreamShape inputShape;
    final MatchKind matchKind;
    //创建TerminalSink的函数
    final Supplier<BooleanTerminalSink<T>> sinkSupplier;
MatchOp(StreamShape shape,
        MatchKind matchKind,
        Supplier&lt;BooleanTerminalSink&lt;T&gt;&gt; sinkSupplier) {
    this.inputShape = shape;
    this.matchKind = matchKind;
    this.sinkSupplier = sinkSupplier;
}

@Override
public int getOpFlags() {
    return StreamOpFlag.IS_SHORT_CIRCUIT | StreamOpFlag.NOT_ORDERED;
}

@Override
public StreamShape inputShape() {
    return inputShape;
}

@Override
public &lt;S&gt; Boolean evaluateSequential(PipelineHelper&lt;T&gt; helper,
                                      Spliterator&lt;S&gt; spliterator) {
    //通过sink#getAndClearState()返回匹配结果
    return helper.wrapAndCopyInto(sinkSupplier.get(), spliterator).getAndClearState();
}

}
复制代码

可以看到实现基本上与其它的TerminalOp相同,只是这里要注意两点:

  • 它通过sinkSupplier工厂方法函数提供的封装终止操作逻辑的sink,讲方法时会详解。
  • getOpFlags()方法返回StreamOpFlag.IS_SHORT_CIRCUIT | StreamOpFlag.NOT_ORDERED,代表短路操作。在触发数据处理逻辑时,通过这个标志走短路的遍历逻辑。

AbstractPipeline#copyInto():

final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    Objects.requireNonNull(wrappedSink);
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
    wrappedSink.begin(spliterator.getExactSizeIfKnown());
    spliterator.forEachRemaining(wrappedSink);
    wrappedSink.end();
}
//短路操作走这里
else {
    copyIntoWithCancel(wrappedSink, spliterator);
}

}
复制代码

AbstractPipeline#copyIntoWithCancel():

final <P_IN> void copyIntoWithCancel(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    @SuppressWarnings({"rawtypes","unchecked"})
    //拿到pipeline链表上的Head对象
    AbstractPipeline p = AbstractPipeline.this;
    while (p.depth > 0) {
        p = p.previousStage;
    }
    wrappedSink.begin(spliterator.getExactSizeIfKnown());
    //注意这里与非短路操作的区别
    p.forEachWithCancel(spliterator, wrappedSink);
    wrappedSink.end();
}
复制代码

短路操作通过遍历pipeline链表,拿到Head节点对象,然后调用它的forEachWithCancel()方法,当然也会在此前后调用begin()和end()方法,就不再展开了。

final void forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
    do { } while (!sink.cancellationRequested() && spliterator.tryAdvance(sink));
}
复制代码

其实forEachWithCancel()就是在do-while循环中调用spliterator#tryAdvance()方法遍历元素,不过每遍历一个元素之前都要满足一个条件:!sink.cancellationRequested(),表示请求没有被取消。下面讲解方法时会细说。

anyMatch()方法

anyMatch()方法,Stream中的元素与Predicate函数匹配,如果有任何一个元素匹配则返回true,否则返回false。

下面进入源码,分析这个方法具体如何实现的,ReferencePipeline#anyMatch():

public final boolean anyMatch(Predicate<? super P_OUT> predicate) {
    return evaluate(MatchOps.makeRef(predicate, MatchOps.MatchKind.ANY));
}
复制代码

同样的套路,通过工厂方法MatchOps#makeRef()创建MatchOp对象:

public static <T> TerminalOp<T, Boolean> makeRef(Predicate<? super T> predicate,
        MatchKind matchKind) {
    Objects.requireNonNull(predicate);
    Objects.requireNonNull(matchKind);
    class MatchSink extends BooleanTerminalSink<T> {
        MatchSink() {
            super(matchKind);
        }
    @Override
    public void accept(T t) {
        //流程未终止且元素匹配结果符合条件
        //则标识流程终止,并将matchKind.shortCircuitResult赋值给匹配结果
        if (!stop &amp;&amp; predicate.test(t) == matchKind.stopOnPredicateMatches) {
            stop = true;
            value = matchKind.shortCircuitResult;
        }
    }
}

//这里通过传入的sinkSupplier函数创建MatchSink实例
return new MatchOp&lt;&gt;(StreamShape.REFERENCE, matchKind, MatchSink::new);

}
复制代码

MatchSink的父类BooleanTerminalSink:

private static abstract class BooleanTerminalSink<T> implements Sink<T> {
    boolean stop;
    boolean value;
BooleanTerminalSink(MatchKind matchKind) {
    value = !matchKind.shortCircuitResult;
}

public boolean getAndClearState() {
    return value;
}

@Override
public boolean cancellationRequested() {
    //匹配成功之后返回true,实现短路
    return stop;
}

}
复制代码

可以看到返回的确实是MatchOp实例,MatchSink是封装终止操作逻辑的sink。先看一下MatchKind枚举类:

enum MatchKind {
    ANY(true, true),
ALL(false, false),

NONE(true, false);

private final boolean stopOnPredicateMatches;
private final boolean shortCircuitResult;

private MatchKind(boolean stopOnPredicateMatches,
                  boolean shortCircuitResult) {
    this.stopOnPredicateMatches = stopOnPredicateMatches;
    this.shortCircuitResult = shortCircuitResult;
}

}
复制代码

MatchKind定义了三种枚举类型ANY、ALL、NONE,这里着重关注ANY:字段stopOnPredicateMatches的值为true,如果Predicate函数与元素匹配返回true,则流程终止;字段shortCircuitResult为true,表示符合匹配规则时,返回给调用方的结果为true,否则返回false。

MatchSink通过stop和value变量,以及MatchKind枚举来控制短路和保存匹配结果。

allMatch()方法

allMatch()方法,Stream中的元素与Predicate函数匹配,如果有任何一个元素不匹配,则返回false,否则就表示所有元素都匹配,返回true。注意这里说的比较拗口,主要是为了体现这个方法也是一个短路操作。

ReferencePipeline#allMatch()方法跟anyMatch()差不多,只是传入的MatchKind是ALL类型,就不再展示说明了。

public final boolean allMatch(Predicate<? super P_OUT> predicate) {
    return evaluate(MatchOps.makeRef(predicate, MatchOps.MatchKind.ALL));
}
复制代码

noneMatch()方法

noneMatch()方法,其实就是allMatch()方法的否定,Stream中的元素与Predicate函数匹配,如果有任何一个元素匹配,则返回false,否则就表示所有元素都不匹配,返回true。

ReferencePipeline#noneMatch()方法,创建MatchOp实例时传入的是NONE类型。

public final boolean noneMatch(Predicate<? super P_OUT> predicate) {
    return evaluate(MatchOps.makeRef(predicate, MatchOps.MatchKind.NONE));
}
复制代码

FindOp

FindOp是一种查找Stream中元素的短路操作,当找到一个结果时,终止元素遍历,直接返回结果。

private static final class FindOp<T, O> implements TerminalOp<T, O> {
    //查找的元素的类型
    private final StreamShape shape;
   //是否必须满足查找的是元素中的第一个元素
    final boolean mustFindFirst;
    //Stream中没有元素时,返回的值
    final O emptyValue;
    //判断元素存在的函数
    final Predicate<O> presentPredicate;
    //提供创建封装终止操作逻辑的工厂方法函数
    final Supplier<TerminalSink<T, O>> sinkSupplier;
FindOp(boolean mustFindFirst,
               StreamShape shape,
               O emptyValue,
               Predicate&lt;O&gt; presentPredicate,
               Supplier&lt;TerminalSink&lt;T, O&gt;&gt; sinkSupplier) {
    this.mustFindFirst = mustFindFirst;
    this.shape = shape;
    this.emptyValue = emptyValue;
    this.presentPredicate = presentPredicate;
    this.sinkSupplier = sinkSupplier;
}

@Override
public int getOpFlags() {
    return StreamOpFlag.IS_SHORT_CIRCUIT | (mustFindFirst ? 0 : StreamOpFlag.NOT_ORDERED);
}

@Override
public StreamShape inputShape() {
    return shape;
}

@Override
public &lt;S&gt; O evaluateSequential(PipelineHelper&lt;T&gt; helper,
                                Spliterator&lt;S&gt; spliterator) {
    //数据处理逻辑,并且调用sink#get()方法获取查找结果
    O result = helper.wrapAndCopyInto(sinkSupplier.get(), spliterator).get();
    return result != null ? result : emptyValue;
}

}
复制代码

咋一看,这个类的成员变量非常多,控制逻辑应该很复杂,其实mustFindFirst和presentPredicate都是用于并行流处理过程的。剩下的跟MatchOp非常相似,下面分析具体方法的时候再详解。

findFirst()方法

findFirst()方法,返回一个Optional,这个Optional中的值是Stream的第一个元素,如果Stream中没有元素,则返回Optional#empty()。

在ReferencePipeline#findFirst()方法中,通过FindOps#makeRef()创建FindOp对象:

public final Optional<P_OUT> findFirst() {
    return evaluate(FindOps.makeRef(true));
}
复制代码

FindOps#makeRef()方法:

public static <T> TerminalOp<T, Optional<T>> makeRef(boolean mustFindFirst) {
    return new FindOp<>(mustFindFirst, StreamShape.REFERENCE, Optional.empty(),
                        Optional::isPresent, FindSink.OfRef::new);
}
复制代码

这里的sinkSupplier创建的是FindSink.OfRef实例:

static final class OfRef<T> extends FindSink<T, Optional<T>> {
    @Override
    public Optional<T> get() {
        return hasValue ? Optional.of(value) : null;
    }
}
复制代码

FindSink.OfRef只是实现了get()方法,用于获取查找结果,但是它继承了FindSink:

private static abstract class FindSink<T, O> implements TerminalSink<T, O> {
    boolean hasValue;
    T value;
FindSink() {} // Avoid creation of special accessor

@Override
public void accept(T value) {
    if (!hasValue) {
        hasValue = true;
        this.value = value;
    }
}

@Override
public boolean cancellationRequested() {
    return hasValue;
}

}
复制代码

FindSink通过变量hasValue控制短路流程,当接收到一个元素时,使用value保存结果,并且终止遍历。

findAny()方法

findAny()方法,返回一个Optional,这个Optional中的值是Stream中的一个元素,如果Stream中没有元素,则返回Optional#empty()。注意在并行流调用中,返回的不一定是Stream的第一个元素,但如果是串行流,则行为跟findFirst()一样,Optional中的值就是Stream第一个元素。

public final Optional<P_OUT> findFirst() {
    return evaluate(FindOps.makeRef(true));
}
复制代码

总结

本文先解释了Stream调用过程中,终止操作是所声明的lambda表达式的触发时机,讲解了中间操作调用时的链表封装和终止操作的调用流程。依据是否完整遍历Stream元素,将终止操作划分为非短路操作和短路操作,通过带领大家阅读源码,深入分析了ForEachOp、ReduceOp、MatchOp和FindOp四种终止操作的实现原理,以及每一个终止操作方法是如何实现的。

写在最后

  • 本篇文章是本系列的第四章,相信大家完整阅读之后,对Stream的整体工作流程和原理有了更深入的理解。
  • 在讲解ReduceOp时说到collect()方法拥有分组规约的能力,能够帮助开发者简化很多流程代码、提升开发效率,下一章我们讲深入其中,看看它为何具有如此强大的功能,以及当Collectors中提供的功能不能满足我们的需要时,如何扩展相应的能力。

最后,原创不易,如果觉得本系列文章对您有帮助,能够加深您对Stream原理和源码的理解的话,请不要吝啬您手中的赞(✪ω✪)!

来源:https://juejin.cn/post/7103718952626290695
posted @ 2022-07-13 23:33  程序员小明1024  阅读(94)  评论(0编辑  收藏  举报