Java Stream流与Optional流浅析

92 阅读7分钟

Stream流

1. 操作类型

Stream API中的操作类型如下图所示 图图在图中可以看出,操作类型大概范围两类,每个大类中有两个子项 1. 中间操作:中间操作只是一种标记,只有结束操作才会触发实际计算

  • 无状态:指元素的处理不受前面元素的影响;
  • 有状态:有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果。

2.终止操作:顾名思义,就是得出最后计算结果的操作

  • 短路操作:指不用处理全部元素就可以返回结果;
  • 非短路操作:指必须处理所有元素才能得到最终结果。

2. Stream的操作过程

首先 准备了一些示例代码

	public  class TestStream {  
  
        public TestStream testMap() {  
            System.out.println("测试map方法");  
            return this;        }  
  
        public boolean testBool() {  
            System.out.println("测试boolean方法");  
            return false;        }  
  
        public boolean testTrue() {  
            System.out.println("测试 boolean true方法");  
            return true;        }  
          
        public static void main(String[] args) {  
            List<TestStream> testStreams= Lists.newArrayList(new TestStream(),new TestStream(),new TestStream());  
            Stream<TestStream> result = testStreams  
                    .stream()  
                    .map(TestStream::testMap)  
                    .filter(TestStream::testBool)  
                    .filter(TestStream::testTrue)  
                    .sorted();  
  
        }  
    }

我们准备了一个TestStream类,在该类中 准备一些测试lambda函数的一些方法。在main方法中,执行了一个相关的流操作,在控制台中可以看出。没有任何输出,说明Stream并没有真正执行到对应的方法中,因为我们没有写入终止操作,由此可以看出,在终止操作之前,Stream并没有真正的去执行每个中间操作,而是将中间操作记录了下来,在执行终止操作这一行的时候再去执行中间操作。那么具体的记录过程与执行过程是怎么样的呢

2.1 记录过程

点进源码跟入Steam方法,可以看到Collection的Stream方法调用的StreamSupport.stream()方法如下在该方法中,返回了一个ReferencePipeline.Head对象,看命名不难发现,这是记录管道操作的一个头节点对象,这个Head对象也是继承了ReferencePipeline对象的,所以后续的map ,filter等方法实际都是ReferencePipeline对象的方法,在构造方法中也是调用了父类AbstractPipeline类的构造方法,如下

AbstractPipeline(Spliterator<?> source,  
                 int sourceFlags, boolean parallel) {  
    //上一个步骤
    this.previousStage = null;  
    //源数据
    this.sourceSpliterator = source;  
    //头步骤
    this.sourceStage = this;  
    this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;  StreamOpFlag.INITIAL_OPS_VALUE);  
      this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;  
    //执行深度
    this.depth = 0;  
    //是否异步
    this.parallel = parallel;  
}

在Stream中的每一步操作都被定义为一个Stage 这里可以看到在构造方法中定义了一个previousStage 和 sourceStage 即上一个节点与头节点 在类中还有一个nextStage对象 如下图 可以看出stream实际上是构建了一个双向链表来记录每一步操作,那么我们继续看一下list.map()方法

@Override  
@SuppressWarnings("unchecked")  
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) {  
        //记住这个 onWrapSink!
        @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));  
                }  
            };  
        }  
    };  
}

//构造方法
AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {  
    if (previousStage.linkedOrConsumed)  
        throw new IllegalStateException(MSG_STREAM_LINKED);  
    previousStage.linkedOrConsumed = true;  
    //指定上一个Stage的下一步为当前节点
    previousStage.nextStage = this;  
  
    this.previousStage = previousStage;  
    this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;  
    this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);  
    this.sourceStage = previousStage.sourceStage;  
    if (opIsStateful())  
        sourceStage.sourceAnyStateful = true;  
    this.depth = previousStage.depth + 1;  
}

在该方法中,创建了一个StatelessOp对象,这个对象代表无状态的中间操作,该对象同样继承了ReferencePipeline,在该对象的构造方法中,可以看到讲调用该初始化方法的节点定义为上一个节点,并且对应的深度depth 也进行了+1操作。

我们总结一下, stream()方法得到的是HeadStage,之后每一个操作(Operation)都会创建一个新的Stage并以双向链表的形式结合在一起,每个Stage都记录了本身的操作,Stream就以此方式,实现了对操作的记录,注意结束操作不算depth的深度,它也不属于stage,但是我们的示例语句中没有写结束操作的代码,所以在这里提一下 Stream的Lazy机制 当该语句执行完的时候,我们在debug的过程中,并没有发现它进行执行任何map或者filter的逻辑,list也没有被改变,这就是Stream的Lazy机制。它的特点是:Stream直到调用终止操作时才会开始计算,没有终止操作的Stream将是一个静默的无操作指令

Stage相关类如下

2.2 执行过程

在了解执行过程之前,我们应该先了解另一个接口Sink,该接口继承了Consumer接口,在调用map, filter等无状态操作中返回的StatelessOp对象中,覆盖了opWrapSink方法,返回了一个Sink对象,并且将参数中的Sink对象作为构造方法中的参数传入了进去,走进构造方法后,可以看到

public ChainedReference(Sink<? super E_OUT> downstream) {  
    this.downstream = Objects.requireNonNull(downstream);  
}

在该对象中定义了一个downstream,该对象也是一个Sink类型的对象,并且在定义Sink对象时,覆盖了Consumer接口中的accept方法 如下

@Override  
@SuppressWarnings("unchecked")  
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));  
                }  
            };  
        }  
    };  
}

不难看出在执行accept方法时,就是将当前节点的操作结果,传入给downstream继续执行,而这个downstream则是通过onWrapSink方法中传入过来的。

了解了以上这些概念,我们可以走进结束操作,.collect(Collectors.toList());方法,在该方法中通过Collectors定一个另一个ArrayList收集器。并且传入了collect方法中,在该方法中


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定义了一个ReduceOp对象如下



public static <T, I> TerminalOp<T, I>  
makeRef(Collector<? super T, I, ?> collector) {
    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.accept(state, t);  
        }  
  
        @Override  
        public void combine(ReducingSink other) {  
            state = combiner.apply(state, other.state);  
        }  
    }  
    return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {  
        @Override  
        public ReducingSink makeSink() {  
            return new ReducingSink();  
        }  
  
        @Override  
        public int getOpFlags() {  
            return collector.characteristics().contains(Collector.Characteristics.UNORDERED)  
                   ? StreamOpFlag.NOT_ORDERED  
                   : 0;  
        }  
    };  
}

public static <T>  
Collector<T, ?, List<T>> toList() {  
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,  
                               (left, right) -> { left.addAll(right); return left; },  
                               CH_ID);  
}

CollectorImpl(Supplier<A> supplier,  
              BiConsumer<A, T> accumulator,  
              BinaryOperator<A> combiner,  
              Set<Characteristics> characteristics) {  
    this(supplier, accumulator, combiner, castingIdentity(), characteristics);  
}

在makeRef方法中,返回了一个ReduceOp对象,该对象覆盖了makeSink()方法,返回了一个ReducingSink对象。我们继续往下走 ,走进evaluate 方法中


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

public <P_IN> R evaluateSequential(PipelineHelper<T> helper,  
                                   Spliterator<P_IN> spliterator) { 
        //makeSink返回ReducingSink对象 
    return helper.wrapAndCopyInto(makeSink(), spliterator).get();  
}


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


@Override  
@SuppressWarnings("unchecked")  
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {  
    Objects.requireNonNull(sink);  
	//第一次进入的sink为ReducingSink对象
    for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) { 
	    //当前这个p位调用collect方法之前的Stream对象
	    //在中间操作方法中,覆盖了onWrapSink方法,并且将传入的sink 定义成自己的下一个sink
        sink = p.opWrapSink(p.previousStage.combinedFlags, sink);  
    }  
    return (Sink<P_IN>) sink;  
}



可以看出 wrapsink方法中,是查找链表的头节点,并且调用每个节点的onWrapSink方法,在该方法中 传入当前节点的sink对象,并且将传入的对象定义成自己的下游,形成一个,从头节点到尾部节点的Sink单向链表如图所示 在wrapSink中,通过一层层的前置包装,返回头节点的Sink类传入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);  
    }  
}

在该方法中,先调用了wrappedSink.begin()方法 该方法默认实现为调用downstream的begin方法。相当于触发全部Sink的begin方法,做好运行前的准备。

具体循环的执行则是在spliterator.forEachRemaining(wrappedSink);方法中,操作如下

public void forEachRemaining(Consumer<? super E> action) {  
    int i, hi, mc; // hoist accesses and checks from loop  
    ArrayList<E> lst; Object[] a;  
    if (action == null)  
        throw new NullPointerException();  
    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) {  
                @SuppressWarnings("unchecked") E e = (E) a[i];  
                //执行accept
                action.accept(e);  
            }  
            if (lst.modCount == mc)  
                return;  
        }  
    }  
    throw new ConcurrentModificationException();  
}

在forEachRemaining方法中,调用了accept方法 也就是在定义onWrapSink方法中 初始化Sink对象后定义的accept方法,将自己的执行结果传入downstream继续执行,也就是说,在调用结束操作后才实际执行每个方法。 在实际执行过后,在执行end方法进行结束操作,stream整体的流操作大概 就是如此。了解了大概远离后可以找一些常用的case来分析一下

2.3 具体分析

  1. 无状态操作,大部分无状态操作没有什么特殊处理,均是采用默认的begin end操作 修改部分accept动作,只是做一些数据的过滤以及修改操作
  2. 有状态操作: sorted() 看源码可以发现,在sorted方法中返回了一个OfRef对象,该对象继承StateFulOp属于一个有状态的操作,在该类中实现了onWrapSink方法如下
public Sink<T> opWrapSink(int flags, Sink<T> sink) {  
    Objects.requireNonNull(sink);  
  
    // If the input is already naturally sorted and this operation  
    // also naturally sorted then this is a no-op   
     if (StreamOpFlag.SORTED.isKnown(flags) && isNaturalSort) 
     //如果已经排序过了,就不在排序了 
        return sink;  
    else if (StreamOpFlag.SIZED.isKnown(flags))  
    //如果数量是已知的则使用数组方式
        return new SizedRefSortingSink<>(sink, comparator);  
        //数量未知选择arraylist
    else        return new RefSortingSink<>(sink, comparator);  
}

一般情况下都会选择list作为排序容器,大部分情况下都是不知道容器大小的,于是采用RefSortingSink类作为当前节点处理类,该类代码如下

private static final class RefSortingSink<T> extends AbstractRefSortingSink<T> {  
    private ArrayList<T> list;  
  
    RefSortingSink(Sink<? super T> sink, Comparator<? super T> comparator) {  
        super(sink, comparator);  
    }  
  
    @Override  
    public void begin(long size) {  
        if (size >= Nodes.MAX_ARRAY_SIZE)  
            throw new IllegalArgumentException(Nodes.BAD_SIZE);  
        list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();  
    }  
  
    @Override  
    public void end() {  
        list.sort(comparator);  
        downstream.begin(list.size());  
        if (!cancellationRequestedCalled) {  
            list.forEach(downstream::accept);  
        }  
        else {  
            for (T t : list) {  
                if (downstream.cancellationRequested()) break;  
                downstream.accept(t);  
            }  
        }  
        downstream.end();  
        list = null;  
    }  
  
    @Override  
    public void accept(T t) {  
        list.add(t);  
    }  
}

可以看到该Sink中的accept方法中,并没有执行下游的accept方法,而是将所有的数据装入了一个ArrayList,在end方法利用arrayList进行排序,并且继续开启后续的循环操作

3. 代码建议

  1. 短路操作不一定会执行所有元素
  2. sort方法一定将sort方法前的方法全部执行完毕后在执行后续的方法,所以sort方法 尽量在所有filter操作执行完成后 在进行sort