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
Streamにbreakメカニズムが組み込まれていると便利ですが、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のにあります。