So brechen Sie von Java Stream forEach ab

1. Übersicht

Als Java-Entwickler schreiben wir häufig Code, der eine Reihe von Elementen durchläuft und für jedes eine Operation ausführt. Die Java 8-Streams-Bibliothek und ihre forEach- Methode ermöglichen es uns, diesen Code sauber und deklarativ zu schreiben.

Dies ähnelt zwar Schleifen, es fehlt jedoch das Äquivalent der break- Anweisung, um die Iteration abzubrechen . Ein Stream kann sehr lang oder möglicherweise unendlich sein . Wenn wir keinen Grund haben, ihn weiter zu verarbeiten, möchten wir ihn abbrechen, anstatt auf sein letztes Element zu warten.

In diesem Tutorial werden einige Mechanismen vorgestellt, mit denen wir eine break- Anweisung für eine Stream.forEach- Operation simulieren können .

2. Java 9s Stream.takeWhile ()

Nehmen wir an, wir haben einen Stream von String- Elementen und möchten seine Elemente verarbeiten, solange ihre Längen ungerade sind.

Probieren wir die Java 9 Stream.takeWhile- Methode aus:

Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck") .takeWhile(n -> n.length() % 2 != 0) .forEach(System.out::println);

Wenn wir dies ausführen, erhalten wir die Ausgabe:

cat dog

Vergleichen wir dies mit dem entsprechenden Code in einfachem Java unter Verwendung einer for- Schleife und einer break- Anweisung, um zu sehen, wie es funktioniert:

List 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); } 

Wie wir sehen können, können wir mit der takeWhile- Methode genau das erreichen, was wir brauchen.

Aber was ist, wenn wir Java 9 noch nicht übernommen haben? Wie können wir mit Java 8 etwas Ähnliches erreichen?

3. Ein benutzerdefinierter Spliterator

Erstellen wir einen benutzerdefinierten Spliterator , der als Dekorator für einen Stream.spliterator fungiert . Wir können diesen Spliterator dazu bringen , die Pause für uns durchzuführen .

Zuerst holen wir den Spliterator aus unserem Stream, dann dekorieren wir ihn mit unserem CustomSpliterator und stellen das Prädikat zur Steuerung des Unterbrechungsvorgangs bereit . Schließlich erstellen wir einen neuen Stream aus dem CustomSpliterator:

public static  Stream takeWhile(Stream stream, Predicate predicate) { CustomSpliterator customSpliterator = new CustomSpliterator(stream.spliterator(), predicate); return StreamSupport.stream(customSpliterator, false); }

Schauen wir uns an, wie Sie den CustomSpliterator erstellen :

public class CustomSpliterator extends Spliterators.AbstractSpliterator { private Spliterator splitr; private Predicate predicate; private boolean isMatched = true; public CustomSpliterator(Spliterator splitr, Predicate predicate) { super(splitr.estimateSize(), 0); this.splitr = splitr; this.predicate = predicate; } @Override public synchronized boolean tryAdvance(Consumer consumer) { boolean hadNext = splitr.tryAdvance(elem -> { if (predicate.test(elem) && isMatched) { consumer.accept(elem); } else { isMatched = false; } }); return hadNext && isMatched; } }

Schauen wir uns also die tryAdvance- Methode an. Wir können hier sehen, dass der benutzerdefinierte Spliterator die Elemente des dekorierten Spliterators verarbeitet . Die Verarbeitung erfolgt, solange unser Prädikat übereinstimmt und der ursprüngliche Stream noch Elemente enthält. Wenn eine der Bedingungen falsch wird , "bricht" unser Spliterator und der Streaming-Vorgang endet.

Testen wir unsere neue Hilfsmethode:

@Test public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() { Stream initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck"); List result = CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0) .collect(Collectors.toList()); assertEquals(asList("cat", "dog"), result); }

Wie wir sehen können, wurde der Stream gestoppt, nachdem die Bedingung erfüllt war. Zu Testzwecken haben wir die Ergebnisse in einer Liste zusammengefasst, aber wir hätten auch einen forEach- Aufruf oder eine der anderen Funktionen von Stream verwenden können .

4. Ein Custom forEach

Das Bereitstellen eines Streams mit dem eingebetteten Unterbrechungsmechanismus kann zwar nützlich sein, es kann jedoch einfacher sein, sich nur auf die forEach- Operation zu konzentrieren.

Verwenden wir den Stream.spliterator direkt ohne Dekorator:

public class CustomForEach { public static class Breaker { private boolean shouldBreak = false; public void stop() { shouldBreak = true; } boolean get() { return shouldBreak; } } public static  void forEach(Stream stream, BiConsumer consumer) { Spliterator spliterator = stream.spliterator(); boolean hadNext = true; Breaker breaker = new Breaker(); while (hadNext && !breaker.get()) { hadNext = spliterator.tryAdvance(elem -> { consumer.accept(elem, breaker); }); } } }

Wie wir sehen können, ruft die neue benutzerdefinierte forEach- Methode einen BiConsumer auf , der unseren Code sowohl mit dem nächsten Element als auch mit einem Breaker-Objekt bereitstellt , mit dem der Stream gestoppt werden kann.

Probieren wir dies in einem Unit-Test aus:

@Test public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() { Stream initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck"); List 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. Schlussfolgerung

In diesem Artikel haben wir nach Möglichkeiten gesucht, das Äquivalent zum Aufrufen von break in einem Stream bereitzustellen . Wir haben gesehen, wie Java 9s takeWhile das meiste Problem für uns löst und wie man eine Version davon für Java 8 bereitstellt.

Schließlich haben wir uns eine Dienstprogrammmethode angesehen, die uns das Äquivalent einer Unterbrechungsoperation beim Iterieren in einem Stream liefern kann .

Der Beispielcode ist wie immer auf GitHub zu finden.