Java Stream forEachを中断する方法

1. 概要

Java開発者として、多くの場合、要素のセットを反復処理し、各要素に対して操作を実行するコードを記述します。 link:/java-8-streams-introduction[Java 8ストリームライブラリ]およびその_https://www.baeldung.com/foreach-java [forEach] _メソッドにより、そのコードを記述できます。クリーンで宣言的な方法で。
これはループに似ていますが、**反復を中止するための_break_ステートメントに相当するものがありません*。 *ストリームは非常に長い、または潜在的に無限*である可能性があり、処理を続行する理由がない場合は、最後の要素を待つのではなく、中断します。
このチュートリアルでは、* Stream.forEach_操作で_break_ステートメントをシミュレートできるメカニズムをいくつか見ていきます。*

2. Java 9の_Stream.takeWhile()_

_String_アイテムのストリームがあり、その長さが奇数である限りその要素を処理したいとします。
Java 9 _Stream.takeWhile_メソッドを試してみましょう。
Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck")
  .takeWhile(n -> n.length() % 2 != 0)
  .forEach(System.out::println);
これを実行すると、出力が得られます。
cat
dog
_for_ループと_break_ステートメントを使用して、これをプレーンJavaの同等のコードと比較して、どのように機能するかを見てみましょう。
List<String> list = asList("cat", "dog", "elephant", "fox", "rabbit", "duck");
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    if (item.length() % 2 == 0) {
        break;
    }
    System.out.println(item);
}
ご覧のとおり、_takeWhile_メソッドを使用すると、必要なものを正確に実現できます。
しかし、* Java 9をまだ採用していない場合はどうなりますか?* Java 8を使用して同様のことを実現するにはどうすればよいですか?

3. カスタム_Spliterator_

  • Stream.spliterator_のdecoratorとして機能するカスタム_Spliteratorを作成しましょう。 **この_Spliterator_に_break_を実行させることができます。

    まず、ストリームから_Spliterator_を取得し、次に_CustomSpliterator_でそれを装飾して、_break_操作を制御する_Predicate_を提供します。 最後に、_CustomSpliterator:_から新しいストリームを作成します
public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
    CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
    return StreamSupport.stream(customSpliterator, false);
}
_CustomSpliterator_の作成方法を見てみましょう。
public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {

    private Spliterator<T> splitr;
    private Predicate<T> predicate;
    private boolean isMatched = true;

    public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
        super(splitr.estimateSize(), 0);
        this.splitr = splitr;
        this.predicate = predicate;
    }

    @Override
    public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
        boolean hadNext = splitr.tryAdvance(elem -> {
            if (predicate.test(elem) && isMatched) {
                consumer.accept(elem);
            } else {
                isMatched = false;
            }
        });
        return hadNext && isMatched;
    }
}
それでは、_tryAdvance_メソッドを見てみましょう。 ここでは、カスタム_Spliterator_が装飾された_Spliterator_の要素を処理することがわかります。 *処理は、述語が一致し、初期ストリームにまだ要素がある限り実行されます。*条件のいずれかが_false_になると、_Spliterator_ _ "breaks" _およびストリーミング操作が終了します。
新しいヘルパーメソッドをテストしてみましょう。
@Test
public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream =
      Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");

    List<String> result =
      CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0)
        .collect(Collectors.toList());

    assertEquals(asList("cat", "dog"), result);
}
ご覧のとおり、条件が満たされた後、ストリームは停止しました。 テストの目的で、結果をリストに収集しましたが、_forEach_呼び出しまたは_Stream_のその他の関数を使用することもできます。

4. カスタム_forEach_

_break_メカニズムを組み込んだ_Stream_を提供することは有用ですが、_forEach_操作のみに焦点を合わせる方が簡単な場合があります。
デコレータなしで** _ St​​ream.spliterator _ **を直接使用しましょう:
public class CustomForEach {

    public static class Breaker {
        private boolean shouldBreak = false;

        public void stop() {
            shouldBreak = true;
        }

        boolean get() {
            return shouldBreak;
        }
    }

    public static <T> void forEach(Stream<T> stream, BiConsumer<T, Breaker> consumer) {
        Spliterator<T> spliterator = stream.spliterator();
        boolean hadNext = true;
        Breaker breaker = new Breaker();

        while (hadNext && !breaker.get()) {
            hadNext = spliterator.tryAdvance(elem -> {
                consumer.accept(elem, breaker);
            });
        }
    }
}
ご覧のように、新しいcustom _forEach_メソッドは、https://www.baeldung.com/java-8-functional-interfaces [_BiConsumer_]を呼び出して、次の要素と、停止に使用できるブレーカーオブジェクトの両方をコードに提供します。ストリーム。
単体テストでこれを試してみましょう。
@Test
public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
    List<String> result = new ArrayList<>();

    CustomForEach.forEach(initialStream, (elem, breaker) -> {
        if (elem.length() % 2 == 0) {
            breaker.stop();
        } else {
            result.add(elem);
        }
    });

    assertEquals(asList("cat", "dog"), result);
}

5. 結論

この記事では、ストリームで_break_を呼び出すのと同等の方法を提供しました。 Java 9の_takeWhile_がどのように私たちのためにほとんどの問題を解決するか、そしてそのバージョンをJava 8に提供する方法を見ました。
最後に、_Stream_を繰り返し処理しながら、_break_操作に相当するものを提供できるユーティリティメソッドを調べました。
いつものように、サンプルコードはhttps://github.com/eugenp/tutorials/tree/master/java-streams-2[GitHubで]にあります。