Java でストリーム操作を連鎖させる必要があるのはなぜですか? [重複] 質問する

Java でストリーム操作を連鎖させる必要があるのはなぜですか? [重複] 質問する

私が研究したすべてのリソースでは、ストリームは一度しか消費できず、消費はいわゆるターミナル操作によって行われるということが何らかの形で強調されていると思います (これは私にとって非常に明白です)。

好奇心からこれを試してみました:

import java.util.stream.IntStream;

class App {
    public static void main(String[] args) {
        IntStream is = IntStream.of(1, 2, 3, 4);
        is.map(i -> i + 1);
        int sum = is.sum();
    }
}

最終的にランタイム例外がスローされます。

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
    at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
    at App.main(scratch.java:10)

これはよくあることですが、私は何かを見逃していますが、それでも質問したいです:私が知る限り、map中間(そして怠惰な)操作であり、何もないストリーム自体にのみ存在します。ターミナル操作sum(先行操作)が呼び出された場合にのみ、ストリームは消費されたそして実施された

しかし、なぜそれらを連鎖させなければならないのでしょうか?

違いは何ですか

is.map(i -> i + 1);
is.sum();

そして

is.map(i -> i + 1).sum();

?

ベストアンサー1

これを実行すると、次のようになります。

int sum = IntStream.of(1, 2, 3, 4).map(i -> i + 1).sum();

連鎖したメソッドはすべて呼び出されている戻り値についてチェーン内の前のメソッドの。

何が返されるか、何が返されるかに応じてSomapが呼び出されます。IntStream.of(1, 2, 3, 4)summap(i -> i + 1)

ストリーム メソッドを連鎖させる必要はありませんが、次の同等のコードを使用するよりも読みやすく、エラーも発生しにくくなります。

IntStream is = IntStream.of(1, 2, 3, 4);
is = is.map(i -> i + 1);
int sum = is.sum();

これは、質問で示したコードと同じではありません。

IntStream is = IntStream.of(1, 2, 3, 4);
is.map(i -> i + 1);
int sum = is.sum();

ご覧のとおり、 によって返される参照を無視していますmap。これがエラーの原因です。


編集(コメントによると、これを指摘してくれた@IanKempに感謝します):実はこれは外部のエラーの原因。よく考えてみると、map何かが起こっているはずです内部的にストリーム自体に適用しないと、ターミナル操作はmap各要素に渡される変換をどのようにトリガーするのでしょうか? 中間操作は遅延される、つまり呼び出されたときにストリームの要素に対して何もしないという点には同意します。ただし、内部的には、後で適用できるように、ストリーム パイプライン自体に何らかの状態を構成する必要があります。

詳細はすべて把握していませんが、概念的にはmap少なくとも次の 2 つのことが行われています。

  1. どこかに引数として渡された関数を保持する新しいストリームを作成して返すので、後でターミナル操作が呼び出されたときに要素に適用できます。

  2. また、古いストリーム インスタンス (つまり、呼び出されたインスタンス) にフラグを設定し、このストリーム インスタンスがパイプラインの有効な状態を表さなくなったことを示します。これは、渡された関数を保持する新しい更新された状態が、map返されたインスタンスによってカプセル化されるようになったためです。(この決定は、JDK チームによって、適用される関数を保持しない無効/古い状態でパイプラインを続行してターミナル操作が予期しない結果を返すのを防ぐため、つまり早期に例外をスローすることによって、できるだけ早くエラーが表示されるようにするために行われたのではないかと思います)。

その後、無効としてフラグが付けられたこのインスタンスでターミナル操作が呼び出されると、次のようになりますIllegalStateException。上記の 2 つの項目は、エラーの深い内部原因を構成します。


これをすべて確認する別の方法はStream、中間操作または終端操作のいずれかによって、インスタンスが 1 回だけ操作されるようにすることです。ここでは、同じインスタンスでmapと を呼び出しているため、この要件に違反しています。sum

実際には、JavadocsのStream明確に述べます:

ストリームは、中間ストリーム操作または終端ストリーム操作の呼び出しによって 1 回だけ操作する必要があります。これにより、たとえば、同じソースが 2 つ以上のパイプラインにフィードされる「フォーク」ストリームや、同じストリームの複数のトラバーサルが除外されます。ストリーム実装は、IllegalStateExceptionストリームが再利用されていることを検出すると、例外をスローする場合があります。ただし、一部のストリーム操作は、新しいストリーム オブジェクトではなくレシーバを返すため、すべてのケースで再利用を検出できるとは限りません。

おすすめ記事