java流的中間操作原始碼解析

dust1發表於2018-10-11

Stream的操作分為中間操作和終端操作。

中間操作: 由於stream的延遲性,中間操作相當於“我打算要進行這個操作,但是現在先不急著操作。”例如filter,sort,limit等。

終端操作:執行流的所有中間操作並且關閉流,使得流無法被複用。

中間操作

中間操作是對流中元素的一種操作,這個操作可以化為一個函式,實際上原始碼也是這樣設計的。因此這裡我直接舉filter操作為例子。

首先我們看在interface Stream中對filter的定義.

Stream<T> filter(Predicate<? super T> predicate);
複製程式碼

返回由與此給定謂詞匹配的此流的元素組成的流。

顯然傳入的是一個函式。

這時候往下看它的實現:

@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
    Objects.requireNonNull(predicate);
    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);
                }

                @Override
                public void accept(P_OUT u) {
                    if (predicate.test(u))
                        downstream.accept(u);
                }
            };
        }
    };
}
複製程式碼

我們可以看到,實現中使用了new,目前我認為在這個實現中並沒有對源流的資料進行操作或者說它可能另外複製了一份流進行返回(雖然第二種可能性很低,因為效率的開銷太大)。

然後再看opWrapSink方法,該方法使用了傳入的函式

@Override
public void accept(P_OUT u) {
    if (predicate.test(u))
        downstream.accept(u);
}
複製程式碼

根據我做資料過濾的經歷downstream.accept(u);操作應該是一個新增資料的操作。點進去檢視之後發現他也是個函式

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    /** andThen */
}
複製程式碼

而ChainedReference這個類只是Sink介面中部分實現Sink的抽象類。而Sink繼承了Consumer函式介面

java流的中間操作原始碼解析

而更上一層返回的StatelessOp類,他也是一個繼承了ReferencePipeline並部分實現的抽象類。這裡我們看到,StatelessOp是一個ReferencePipeline類,而ReferencePipeline類我在最早的StreamSupport的stream中已經知道了是一個原始流的類。也就是說它是一個新的流。那麼它和舊的流之間是什麼關係呢?

java流的中間操作原始碼解析

我們直接看最底層的構造方法的賦值

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;
    if (opIsStateful())
        sourceStage.sourceAnyStateful = true;
    this.depth = previousStage.depth + 1;
}
複製程式碼

其中previousStage是舊的流,原始碼中稱它為“上游管道”。然後我們可以看到

previousStage.nextStage = this;
複製程式碼

這行程式碼,到了這裡既視感就很強烈了,這不就是連結串列嗎?!那麼根據原始碼我們可以知道stream中表示資料的Pipeline和中間操作新建立的Pipeline是連結串列關係,那麼其他操作是不是一樣呢?這裡我快速檢視了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) {
        @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物件。

java流的中間操作原始碼解析

相關文章