Java8 Stream实现原理,源码浅析(下)

前言

上文Java8 Stream实现原理,源码浅析(上)我们介绍了流的创建和中间操作,下面讲一讲Stream的终止操作

三、Stream的终止

终止操作的分类与共性

终止操作又分为「短路操作」和「非短路操作」。

之后又根据不同作用分类。

下面列出一些常用API,读者可以尝试观察它们的共同点和不同点。

短路操作:可以提前终止的操作

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

cancellationRequested():如果返回true,表示sink不再处理Stream中后续的元素,用于短路操作。

匹配操作:MatchOp

anyMatch

有一个匹配就立刻返回true

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

有一个不匹配就立刻返回false

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

查找操作:FindOp

findFirst:返回第一个元素

虽然名字叫find但findAny在并行流就是随便返回集合内一个元素,在串行流,就是返回集合的首元素,挺没用的,不支持自定义过滤。要自己匹配还得用match。

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

findAny在串行流与findFirst完全相同,如果是并行流,不一定是第一个。

非短路操作

遍历:ForEachOp

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

forEachOrdered :在串行流中,与forEeah的结果没区别。这个是为了保证并行流对元素的处理顺序是从前往后

聚合:ReduceOp

reduce:聚合
java 复制代码
@Override
public final Optional<P_OUT> reduce(BinaryOperator<P_OUT> accumulator) {
    return evaluate(ReduceOps.makeRef(accumulator));
}
max/min:求最大最小元素
java 复制代码
public final Optional<P_OUT> max(Comparator<? super P_OUT> comparator) {
    return reduce(BinaryOperator.maxBy(comparator));
}

也就是max/min实际调用的是上面这个reduce方法

collect:将流转化为集合
java 复制代码
public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
    A container;
    // 去掉了并行流的判断处理
        container = evaluate(ReduceOps.makeRef(collector));
    return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
           ? (R) container
           : collector.finisher().apply(container);
}

终止操作的流程🚩

  1. 所有方法都根据作用分类成了不同的Op,它们的父类都是TerminalOp。调用了对应的xxxOps工厂类的makeRef方法得到一个TerminalOp的实现类。
  2. 执行evaluate方法。

简单说,就是利用工厂类,根据你的特点,生成一个终止操作:TerminalOp,然后evaluate

1、TerminalOp:终止操作

这里插一嘴,TerminalOp不像前面都继承自pipeline。TerminalOp自己就是个顶层的父接口

并且TerminalOp并不会像其他中间操作一样仍返回一个Stream

java 复制代码
interface TerminalOp<E_IN, R> {
    
    default StreamShape inputShape() { return StreamShape.REFERENCE; }
    default int getOpFlags() { return 0; }
​
    // 并行 evaluate
    default <P_IN> R evaluateParallel(PipelineHelper<E_IN> helper,
                                      Spliterator<P_IN> spliterator) {
        // 简略了一下
        return evaluateSequential(helper, spliterator);
    }
    // 串行 evaluate 抽象方法
    <P_IN> R evaluateSequential(PipelineHelper<E_IN> helper,
                                Spliterator<P_IN> spliterator);
}

2、evaluate():执行操作

java 复制代码
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
   // 忽略校验
    return isParallel()
           ?  // 并行
 terminalOp.evaluateParallel(this,sourceSpliterator(terminalOp.getOpFlags()))
           :  // 串行             
 terminalOp.evaluateSequential(this,sourceSpliterator(terminalOp.getOpFlags()));
}

也就是真正的执行是terminalOp.evaluateSequential方法,是写在terminalOp内部的

那么核心点就在于terminalOp了。

四大TerminalOp架构/共同点

  • 都是工厂模式,由xxxOps.makeRef方法创建对象。
  • 重写了evaluateXXX方法,即一个Stream流语句的最终执行
  • 都实现了TerminalOp接口
  • evaluateXXX方法最终都是调用pipeline的wrapAndCopyInto方法

ForEachOp

java 复制代码
static abstract class ForEachOp<T>
        implements TerminalOp<T, Void>, TerminalSink<T, Void> {
    private final boolean ordered;
}

TerminalOp接口:前文介绍过,主要是提供了evaluate方法的具体实现

TerminalSink接口:继承了Sink和Supplier,Sink继承自Consumer,有消费能力的同时提供了begin/end。Supplier也是lambda表达式,无参有返回值。

重点是重写了evaluate方法

java 复制代码
// 因为foreach没有返回值,所以get返回null this就是当前的TerminalOp
public <S> Void evaluateSequential(PipelineHelper<T> helper,
                                   Spliterator<S> spliterator) {
    return helper.wrapAndCopyInto(this, spliterator).get();
}

ReduceOp

java 复制代码
private static abstract class ReduceOp<T, R, S extends AccumulatingSink<T, R, S>>
        implements TerminalOp<T, R> {
    // makeSink方法抽象
    public abstract S makeSink();
    // 不关注并行了。实现了evaluateSequential方法
    @Override
    public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
                                       Spliterator<P_IN> spliterator) {
        return helper.wrapAndCopyInto(makeSink(), spliterator).get();
    }
}

FindOp

java 复制代码
private static final class FindOp<T, O> implements TerminalOp<T, O> {
    // 核心属性
    private final StreamShape shape;
    final boolean mustFindFirst;
    final O emptyValue;
    final Predicate<O> presentPredicate;
    final Supplier<TerminalSink<T, O>> sinkSupplier;
    // 方法
   public <S> O evaluateSequential(PipelineHelper<T> helper,Spliterator<S> spliterator) {
            O result = helper.wrapAndCopyInto(sinkSupplier.get(), spliterator).get();
            return result != null ? result : emptyValue;
        }    
}

可以看到也是wrapAndCopyInto方法。

MatchOp

java 复制代码
private static final class MatchOp<T> implements TerminalOp<T, Boolean> {
    private final StreamShape inputShape;
    final MatchKind matchKind;
    final Supplier<BooleanTerminalSink<T>> sinkSupplier;
    public <S> Boolean evaluateSequential(PipelineHelper<T> helper,
                                              Spliterator<S> spliterator) {
   return helper.wrapAndCopyInto(sinkSupplier.get(), spliterator).getAndClearState();
        }
}

wrapAndCopyInto方法

四大Op的evaluateSequential方法都调用了pipeline的wrapAndCopyInto方法。

正如方法名:

  1. wrap包装sink
  2. 执行CopyInto方法

wrapAndCopyInto方法的实现在AbstractPipeline

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

1、封装Sink

这里首先有一个wrapSink方法,对sink的封装

java 复制代码
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    for ( AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
        sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
    }
    return (Sink<P_IN>) sink;
}

可以看到,往回遍历双向链表然后调用opWrapSink方法,仍然返回一个sink

opWrapSink方法还记得吗?是所有不管有无状态的Op即中间操作必须重写的方法

这里回顾一下map的:

java 复制代码
    @Override
    Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
       // 返回 Sink.ChainedReference
        return new Sink.ChainedReference<P_OUT, R>(sink) {
            @Override
            public void accept(P_OUT u) {
                // 这个mapper就是 map(mapper) lambda表达式,比如 映射到Id就是 User::getId
                downstream.accept(mapper.apply(u));
            }
        };
    }

ChainedReference有一个核心属性:

java 复制代码
protected final Sink<? super E_OUT> downstream;
// 即下一个的Sink,从而实现Sink的相连

又重写了accept方法,即当前Op先apply结束以后,再调用下一个的Op,从而实现了所有Op的相连,返回了一个最终的Sink。

至此确定了copyInto的wrappedSink参数。

2、执行copyInto:遍历处理

java 复制代码
// wrapAndCopyInto 调用 copyInto
    final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
​
        if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
            wrappedSink.begin(spliterator.getExactSizeIfKnown());
            spliterator.forEachRemaining(wrappedSink);
            wrappedSink.end();
        }
        else {
            copyIntoWithCancel(wrappedSink, spliterator);
        }
    }

SHORT_CIRCUIT是短路的意思,因此如果是短路操作,进入 copyIntoWithCancel;否则forEachRemaining

2.1、若短路操作

java 复制代码
final <P_IN> void copyIntoWithCancel(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
    // 链表尾
    AbstractPipeline p = AbstractPipeline.this;
    while (p.depth > 0) {
        p = p.previousStage;
    }
    
    wrappedSink.begin(spliterator.getExactSizeIfKnown());
    // 依次消费所有元素
    p.forEachWithCancel(spliterator, wrappedSink);
    
    wrappedSink.end();
}
​
    final void forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
        do { } while (!sink.cancellationRequested() && spliterator.tryAdvance(sink));
    }
​
// tryAdvance就是,用lambda表达式的accept方法消费一个元素,sink就是consumer的子类,有accept方法

2.2、若非短路操作

非短路操作:

  • 先begin
  • 后spliterator.forEachRemaining(wrappedSink);
  • 最后end

begin和end都被StatexxxOp重写,而forEachRemaining又被各种spliterator重写

这里以ArrayListSpliterator为例:

java 复制代码
public void forEachRemaining(Consumer<? super E> action) {
    int i, hi, mc; // hoist accesses and checks from loop
    ArrayList<E> lst; Object[] a;
    if ((lst = list) != null && (a = lst.elementData) != null) {
        if ((hi = fence) < 0) {
            mc = lst.modCount;
            hi = lst.size;
        }
        else
            mc = expectedModCount;
        if ((i = index) >= 0 && (index = hi) <= a.length) {
            for (; i < hi; ++i) {
                E e = (E) a[i];
                action.accept(e);
            }
            if (lst.modCount == mc)
                return;
        }
    }
    throw new ConcurrentModificationException();
}

就是遍历处理。

在evaluateSequential方法的最后,通常还有一个get方法,用于获取返回值。

至此,终止操作分析完毕。

小结

终止操作:大概是创建一个TerminalOp,然后调用evaluate方法处理。evaluate最终依赖wrapAndCopyInto,会遍历双向链表来封装一个链式Sink,然后遍历元素调用lambda表达式即sink来处理。

中间操作是如何生效的

非短路下:先begin方法。再accpet。最后end。

一般都是链式调用,我处理完了,给我下一个处理。但是不乏特殊的,比如sorted:

java 复制代码
public void begin(long size) {
    list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
}
public void end() {
    // 排序
    list.sort(comparator);
    downstream.begin(list.size());
    // 非短路 全部处理
    if (!cancellationWasRequested) {
        list.forEach(downstream::accept);
    }
    else {
        // 短路 随时可能会break
        for (T t : list) {
            if (downstream.cancellationRequested()) break;
            downstream.accept(t);
        }
    }
    downstream.end();
    list = null;
}
public void accept(T t) {
    // 并没有向下传递
    list.add(t);
}

begin,accept方法并不是链式调用,相反,begin直接停住了new一个ArrayList,accept是往自己的ArrayList添加元素。那么我们知道后面的accept方法就不会被调用到了,于是开始end。此时又会让下游begin,accept,end走一套。

collect方法的参数:Collector

collect方法与别的终止操作有些不一样。

Collectors.toList()返回一个Collector接口,这个方法做了些什么呢?

Collector接口

java 复制代码
public interface Collector<T, A, R> {
    // 四个lambda方法
    Supplier<A> supplier();
​
    BiConsumer<A, T> accumulator();
​
    BinaryOperator<A> combiner();
​
    Function<A, R> finisher();
    // Characteristics枚举类 : CONCURRENT / UNORDERED / IDENTITY_FINISH
    Set<Characteristics> characteristics();
}

Collector唯一实现:CollectorImpl

java 复制代码
  static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
      // 五个属性 分别对应五个方法
        private final Supplier<A> supplier;
        private final BiConsumer<A, T> accumulator;
        private final BinaryOperator<A> combiner;
        private final Function<A, R> finisher;
        private final Set<Characteristics> characteristics;
    }

实际上,这也是Collector的最大的作用了。

Collector仅仅起到了封装数据(一些lambda表达式)的作用

Collectors工具类

toList():返回ArrayList

java 复制代码
public static <T>Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>(
                        1      (Supplier<List<T>>) ArrayList::new,
                        2       List::add,
                        3       (left, right) -> { left.addAll(right); return left; },
                        4       CH_ID // 这个可以不用管
                              );
}
​
// 上面调用的是这个构造方法
// 1 Supplier 无参,有返回值
// 2 BiConsumer 两个参数,没有返回值
// 3 BinaryOperator 两个入参,有返回值,三者类型相同,在toList这里的类型是ArrayList
// 猜测:这里用 BinaryOperator 应该是为了让并行也能用吧...
      CollectorImpl(Supplier<A> supplier,
                      BiConsumer<A, T> accumulator,
                      BinaryOperator<A> combiner,
                      Set<Characteristics> characteristics) {
           // 依次赋值  finisher比较特殊一点,是 finisher  =  castingIdentity()
        }
  

Collectors.toList()方法生成了一个CollectorImpl实例,实现了Collector接口。传递了三个lambda方法

  • new 一个ArrayList
  • 向ArrayList add元素
  • 向ArrayList addAll另一个ArrayList 的元素
toList的terminalOp
java 复制代码
public static <T, I> TerminalOp<T, I> makeRef(Collector<? super T, I, ?> collector) {
    // 返回一个 TerminalOp
    // supplier 是 ArrayList::new
    Supplier<I> supplier = Objects.requireNonNull(collector).supplier();
    // accumulator 是 List::add
    BiConsumer<I, ? super T> accumulator = collector.accumulator();
    // combiner 是 (left, right) -> { left.addAll(right); return left; }
    BinaryOperator<I> combiner = collector.combiner();
    // ---------------------
    class ReducingSink extends Box<I>
            implements AccumulatingSink<T, I, ReducingSink> {
        // 就是new一个ArrayList
        @Override
        public void begin(long size) {
            state = supplier.get();
        }
        // 向ArrayList add一个元素
        @Override
        public void accept(T t) {
            accumulator.accept(state, t);
        }
        // 向ArrayList add 另一个ArrayList的所有元素
        @Override
        public void combine(ReducingSink other) {
            state = combiner.apply(state, other.state);
        }
    }
    // ---------------------    
    // 以上可以概括为取出CollectorImpl的几个重要的表达式
    return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {
        @Override
        public ReducingSink makeSink() {
            return new ReducingSink();
        }
        // 省略 getOpFlags
    };
}

至此,得到一个ReduceOp实例,并重写了makeSink方法。makeSink用作wrapAndCopyInto方法的入参。

在evaluate时,ReduceOp也会被当成和普通的StatexxxOp一样,被调用begin,end,accept方法,他的sink在整个sinke链条的最后。因此实现了将结果转化为一个ArrayList。

joining():字符串拼接

java 复制代码
String result = Stream.of("java", "scala", "go", "python")
        .collect(Collectors.joining(","));
System.out.println(result);
// 输出 : java,scala,go,python
// 支持无参直接拼接,也支持分隔符

通过分析toList我们已经知道,Collector只是封装几个函数,因此不同方法只是几个lambda表达式方法和别人的不同,因此后文只关注这些方法

joining()就是:

java 复制代码
public static Collector<CharSequence, ?, String> joining() {
    return new CollectorImpl<CharSequence, StringBuilder, String>(
            StringBuilder::new,  // 1
            StringBuilder::append, // 2 
            (r1, r2) -> { r1.append(r2); return r1; },// 3
            StringBuilder::toString, // 4
            CH_NOID);
}

多写了一个方法:将StringBuilder转化为String,多指定了finisher方法,这在ArrayList没有。

toSet():返回HashSet

java 复制代码
public static <T>
Collector<T, ?, Set<T>> toSet() {
    return new CollectorImpl<>(
                               (Supplier<Set<T>>) HashSet::new, 
                               Set::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_UNORDERED_ID);
}
​

toMap():返回HashMap

java 复制代码
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                            Function<? super T, ? extends U> valueMapper,
                            BinaryOperator<U> mergeFunction,
                            Supplier<M> mapSupplier) {
    BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),// 处理k
                                          valueMapper.apply(element), // 处理v
                                          mergeFunction);
    // merge方法可以简单理解为 add 只不过可以自定义key相同的情况,如何处理,下面可以看到源码
    return new CollectorImpl<>(mapSupplier,  // new 
                               accumulator,  // merge
                               mapMerger(mergeFunction),
                               CH_ID);
}

toMap()要稍微特殊一点,可以看到他有个mergeFunction的函数,这个是处理两个key一模一样的情况的。

java 复制代码
// merge是map接口下的方法 主要是利用remappingFunction 决定key相同时,用哪个value
default V merge(K key, V value,
        BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
               remappingFunction.apply(oldValue, value);
    if(newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

除了上述这些内容,Collectors还提供了max/minBy,reuducing,averaging,summing等等方法,就不一一介绍了。

特殊的终止:toArray

这个就不关心实现了,但它和别人确实都不太一样。

java 复制代码
public final Object[] toArray() {
    return toArray(Object[]::new);
}
    public final <A> A[] toArray(IntFunction<A[]> generator) {
        return (A[]) Nodes.flatten(evaluateToArrayNode(generator), generator)
                              .asArray(generator);
    }

小结:Stream语句的原理

第一步,肯定是创建Stream流对象 ,众所周知Stream是个接口。因此我们创建的流的实体类是:Pipeline。用的最多的是ReferencePipeline,他又有三个内部类:Head头节点,无状态操作,和有状态的操作。这两种状态的操作我又理解成普通节点。因为它是已双向链表的形式组织起来所有的Option的,头节点是特殊的Head,其余节点就是这两种Option。创建对象呢又依赖StreamSupport.stream方法。这里又有三种创建流对象的方法底层全都依赖StreamSupport,会返回一个Head头节点。

第二步,就是很多中间操作,比如我们常用的map映射,然后sorted排序这些。这些方法的实现都在AbstractPipeline,是ReferencePipeline的父类。也并不复杂,通常是创建一个节点,就是无状态操作,和有状态操作中的一个,然后插入到链表尾部。然后StatelessOp这也是抽象类,虽然Stream是个惰性流,但我们也要存储你的lambda表达式,所以我们要重写opWrapSink方法,这个方法很重要,会在终止Stream的时候使用。

最后一步,也就是终止操作 。终止操作又有很多种比如collect,foreach这些。这些方法会创建一个TerminalOp对象。TerminalOp又有很多子类,这里我拿collect举例吧,就是ReduceOp,利用ReduceOps的makeRef方法创建对象。如果是collect(Collections.toList())的话他就会向最后这个ReduceOp存储三个lambda方法,就是创建ArrayList,以及ArrayList的add和addAll方法。然后这个ReduceOp还会重写一个方法叫makeSink。这个Sink是一个Consumer的子类,就也是一个lambda表达式。最后呢我们执行evaluate方法,会先调用这个makeSink方法取出TerminalOp的Sink,然后对它做一个封装,就是第二步中间操作提到的重写的opWrapSink方法,我们会从后往前遍历双向链表,依次调用这个opWrapSink方法,那这个方法的逻辑是取决于你的Option的,比如map是返回一个Sink.ChainedReference,首先它内部有一个它后续的Sink,其次重写它的accpet方法就是lambda表达式嘛,会先执行当前的lambda式以后,调用后续Sink的accept方法,以此实现对所有的lambda表达式的依次执行,执行完以后就返回了。

整个流程大概就是这样。

参考文档

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

相关推荐
吾日三省吾码3 小时前
JVM 性能调优
java
Estar.Lee3 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
弗拉唐4 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi774 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
2401_857610035 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
少说多做3435 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀5 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20205 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深5 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++