1. 概要

Java開発者として、私たちは多くの場合、要素のセットを反復処理し、各要素に対して操作を実行するコードを記述します。 Java8ストリームライブラリとそのforEachメソッドを使用すると、そのコードをクリーンで宣言的な方法で記述できます。

これはループに似ていますが、反復を中止するためのbreakステートメントに相当するものがありませんストリームは非常に長くなるか、無限になる可能性があります。処理を続行する理由がない場合は、最後の要素を待つのではなく、ストリームを中断する必要があります。

このチュートリアルでは、Stream.forEach操作でbreakステートメントをシミュレートできるようにするいくつかのメカニズムを見ていきます。

2. Java9の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のデコレータとして機能するカスタムSplitteratorを作成しましょう。 これを作ることができますスプリッターを実行します壊す私たちのために。

まず、ストリームから Spliterator を取得し、次に CustomSpliterator で装飾し、述語を提供してブレークを制御します操作。 最後に、 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メソッドを見てみましょう。 ここでカスタムがわかりますスプリッター装飾された要素を処理しますスプリッター 述語が一致し、初期ストリームに要素が残っている限り、処理は実行されます。 いずれかの条件が間違い 、 私たちのスプリッター 「休憩」 ストリーミング操作が終了します。

新しいヘルパーメソッドをテストしてみましょう。

@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

Streambreakメカニズムが組み込まれていると便利ですが、forEach操作だけに焦点を当てる方が簡単な場合があります。

Stream.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);
            });
        }
    }
}

ご覧のとおり、新しいカスタムforEachメソッドは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を呼び出すのと同等の方法を提供する方法について説明しました。 Java9のtakeWhileが問題のほとんどをどのように解決するか、そしてJava8にそのバージョンを提供する方法を見てきました。

最後に、 Stream を反復処理しながら、break操作に相当するユーティリティメソッドを検討しました。

いつものように、サンプルコードはGitHubにあります。