如何中止迭代Java Stream forEach

1.概述

作為Java開發人員,我們經常編寫在一組元素上迭代並在每個元素上執行操作的代碼。 Java 8流庫及其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

讓我們創建一個自定義的Spliterator ,將其用作Stream.spliterator的裝飾器。我們可以讓這個Spliterator為我們執行break

首先,我們從流中獲取Spliterator ,然後使用CustomSpliterator裝飾它,並提供Predicate來控制break操作。最後,我們將根據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方法。我們在這裡可以看到,自定義的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

儘管為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 ,該BiConsumer為我們的代碼提供了下一個元素和可以用來停止流的breaker對象。

讓我們在單元測試中嘗試一下:

@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操作的功能。

與往常一樣,示例代碼可以在GitHub上找到