2016-01-08 3 views
19

Java 8 APIは言う:パイプラインの端末操作が実行されるまで、パイプラインソースのは、なぜ、このJavaストリームは二度時に作動させますか?

トラバーサルが開始されません。

なぜ以下のコード例外:

java.lang.IllegalStateException:ストリームが既に オペレート又はに従って

Stream<Integer> stream = Stream.of(1,2,3); 
stream.filter(x-> x>1); 
stream.filter(x-> x>2).forEach(System.out::print); 

に第1のフィルタリング動作が閉じられているがAPIはストリーム上で動作するはずではありません。

答えて

26

を呼び出すときにfilterの戻り値を無視しているので、これが発生して処理されます。

Stream<Integer> stream = Stream.of(1,2,3); 
stream.filter(x-> x>1); // <-- ignoring the return value here 
stream.filter(x-> x>2).forEach(System.out::print); 

Stream.filterは、与えられた述語に一致するこのストリームの要素からなる新しいStreamを返します。しかし、新しいストリームであることに注意することが重要です。フィルタがそれに追加されたときに古いものは時に操作されました。しかし、新しいものはありませんでした。 StreamのJavadocから引用

ストリームは、一度だけ(中間または末端ストリームの操作を呼び出す)に操作されなければなりません。

この場合、filterは、古いStreamインスタンスで操作される中間操作です。

だから、このコードは正常に動作します:

Stream<Integer> stream = Stream.of(1,2,3); 
stream = stream.filter(x-> x>1); // <-- NOT ignoring the return value here 
stream.filter(x-> x>2).forEach(System.out::print); 

をブライアン・ゲッツで述べたように、あなたは一般的に一緒にこれらの呼び出しをチェーンになります。

Stream.of(1,2,3).filter(x-> x>1) 
       .filter(x-> x>2) 
       .forEach(System.out::print); 
+0

です。ストリームのトラバースとは異なります。私はストリーム上での操作がそれの内部状態を変更すると仮定します。 –

+0

@ aliasはい、それはストリームの内部状態を変更します。 – mks

0

これは、あなたがそれに複数の.fliter()を添付するときに検出されstreamの誤用です。

は、それはそれは

+0

私はこれを少なくとも*奇妙なエラーメッセージとして見ています。 – Eugene

+0

@Eugene filter()メソッドが呼び出されたときにストリームが処理され、終了されません。 –

3

filter()メソッドは、ストリームを使用して、あなたはあなたの例では無視一つの他のStreamインスタンスを返す「すでに時に操作され」ただけで、それが複数回通過してきたと言っていません。

filterは、中間操作ですが、あなたはあなたのコードは次のように書くべきではない

同じストリームインスタンス上で二回フィルタを呼び出すことはできません。

Stream<Integer> stream = Stream.of(1,2,3); 
           .filter(x-> x>1) 
           .filter(x-> x>2); 
stream.forEach(System.out::print); 

フィルタが中間操作であるように、「何も」これらのメソッドを呼び出すときに行われます。すべての仕事は本当にforEach()方法

+0

「何もしていない」とは思えません。最初のフィルタを呼び出すときに何か「完了」しています。そのため、2番目のフィルタを同じストリームに呼び出すと、そのフィルタが呼び出されません。 – mks

+0

@ MrinalK.Samantaそれは私が引用符を入れた理由です;)私はそれが単に宣言的であり、現時点ではフィルタリングが実際に行われていないことを意味しますが、forEachのような端末操作が呼び出されたときにのみ – Prim

1

ストリーム上のドキュメントは言う:

"ストリームを操作する必要があります(中間またはターミナルstrを呼び出すeam operation)を1回だけ実行します。

これはソースコードで実際に見ることができます。あなたは、フィルタを呼び出すとき、それはコンストラクタ(this)に現在のパイプラインのインスタンスを渡して、新しいステートレス操作を返します。

@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) { 
     .... 
} 

コンストラクタ呼び出しがセットアップされ、このようAbstractPipelineコンストラクタを呼び出す終わる:

AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) { 
    if (previousStage.linkedOrConsumed) 
     throw new IllegalStateException(MSG_STREAM_LINKED); 
    previousStage.linkedOrConsumed = true; 
    ... 
} 

フィルタをソース(2行目)で初めて呼び出すと、ブール値がtrueに設定されます。フィルタによって返された戻り値を再利用しないので、フィルタへの2回目の呼び出し(3行目)は、元のストリームソース(1行目)が既にリンクされていること(最初のフィルタ呼び出しのため)取得する。