Java 8 und Infinite Streams
1. Überblick
In diesem Artikel werden wir uns diejava.util.Stream-API ansehen und sehen, wie wir dieses Konstrukt verwenden können, um mit einem unendlichen Strom von Daten / Elementen zu arbeiten.
Die Möglichkeit, an der unendlichen Folge von Elementen zu arbeiten, beruht auf der Tatsache, dass Streams so aufgebaut sind, dass sie faul sind.
Diese Faulheit wird durch eine Trennung zwischen zwei Arten von Operationen erreicht, die in Streams ausgeführt werden könnten:intermediate undterminal Operationen.
2. Zwischen- und Terminalbetrieb
All Stream operations are divided into intermediate and terminal operations und werden zu Stream-Pipelines kombiniert.
Eine Stream-Pipeline besteht aus einer Quelle (z. B. einemCollection, einem Array, einer Generatorfunktion, einem E / A-Kanal oder einem Generator für unendliche Sequenzen). gefolgt von null oder mehr Zwischenoperationen und einer Terminaloperation.
2.1. Intermediate Operationen
Intermediate Operationen werden nicht ausgeführt Einheit Einigeterminal Operationen werden aufgerufen.
Sie bilden eine Pipeline mit einer Ausführung vonStream. Die Operationintermediatekann mit folgenden Methoden zu einer PipelineStreamhinzugefügt werden:
-
Filter()
-
Karte()
-
flatMap ()
-
deutlich ()
-
sortiert ()
-
spähen()
-
Grenze()
-
überspringen()
AlleIntermediate-Operationen sind faul, daher werden sie erst ausgeführt, wenn tatsächlich ein Ergebnis einer Verarbeitung benötigt wird.
Grundsätzlich gebenintermediate-Operationen einen neuen Stream zurück. Das Ausführen einer Zwischenoperation führt eigentlich keine Operation aus, sondern erstellt einen neuen Stream, der beim Durchlaufen die Elemente des anfänglichen Streams enthält, die mit dem angegebenen Prädikat übereinstimmen.
Daher beginnt das Durchlaufen derStream erst, wenn derterminal-Betrieb der Pipeline ausgeführt wird.
Dies ist eine sehr wichtige Eigenschaft, die insbesondere für unendliche Streams wichtig ist, da damit Streams erstellt werden können, die nur dann aufgerufen werden, wenn eineTerminal-Operation aufgerufen wird.
2.2. Terminal Operationen
Terminal Operationen können den Strom durchlaufen, um ein Ergebnis oder eine Nebenwirkung zu erzeugen.
Nachdem die Terminaloperation ausgeführt wurde, gilt die Stream-Pipeline als verbraucht und kann nicht mehr verwendet werden. In fast allen Fällen sind Terminaloperationen eifrig, da sie das Durchsuchen der Datenquelle und die Verarbeitung der Pipeline vor der Rückkehr abschließen.
Der Eifer einer Terminaloperation ist wichtig für unendliche Ströme, daat the moment of processing we need to think carefully if our Stream is properly bounded by, beispielsweise einelimit()-Transformation. Terminal Operationen sind:
-
für jeden()
-
forEachOrdered ()
-
toArray ()
-
reduzieren()
-
sammeln()
-
Mindest()
-
max ()
-
Anzahl()
-
anyMatch ()
-
allMatch ()
-
noneMatch ()
-
findFirst ()
-
finde irgendein()
Jede dieser Operationen löst die Ausführung aller Zwischenoperationen aus.
3. Unendliche Streams
Nachdem wir diese beiden Konzepte -Intermediate undTerminal - Operationen verstanden haben, können wir einen unendlichen Stream schreiben, der die Faulheit von Streams nutzt.
Nehmen wir an, wir möchten einen unendlichen Strom von Elementen aus Null erstellen, der um zwei erhöht wird. Dann müssen wir diese Reihenfolge einschränken, bevor wir die Terminaloperation aufrufen.
It is crucial to use a limit() method before executing a collect() method ist eine Terminaloperation, andernfalls wird unser Programm auf unbestimmte Zeit ausgeführt:
// given
Stream infiniteStream = Stream.iterate(0, i -> i + 2);
// when
List collect = infiniteStream
.limit(10)
.collect(Collectors.toList());
// then
assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));
Wir haben einen unendlichen Stream mit der Methodeiterate()erstellt. Dann haben wir einelimit()-Transformation und einecollect()-Terminaloperation aufgerufen. Dann haben wir in unseren resultierendenList, die ersten 10 Elemente einer unendlichen Sequenz aufgrund einer Faulheit vonStream.
4. Unendlicher Strom eines benutzerdefinierten Elementtyps
Nehmen wir an, wir möchten einen unendlichen Strom von zufälligenUUIDs erstellen.
Der erste Schritt, um dies mithilfe derStream-API zu erreichen, besteht darin,Supplier dieser Zufallswerte zu erstellen:
Supplier randomUUIDSupplier = UUID::randomUUID;
Wenn wir einen Lieferanten definieren, können wir mit der Methodegenerate()einen unendlichen Stream erstellen:
Stream infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);
Dann könnten wir ein paar Elemente aus diesem Stream nehmen. Wir müssen daran denken, einelimit()-Methode zu verwenden, wenn unser Programm in einer endlichen Zeit beendet werden soll:
List randomInts = infiniteStreamOfRandomUUID
.skip(10)
.limit(10)
.collect(Collectors.toList());
Wir verwenden eineskip()-Transformation, um die ersten 10 Ergebnisse zu verwerfen und die nächsten 10 Elemente zu verwenden. Wir können einen unendlichen Strom von benutzerdefinierten Typelementen erstellen, indem wir eine Funktion derSupplier-Schnittstelle an einegenerate()-Methode auf einemStream übergeben.
6. Do-While - der Stream Way
Nehmen wir an, wir haben eine einfache do..while-Schleife in unserem Code:
int i = 0;
while (i < 10) {
System.out.println(i);
i++;
}
Wir drucken den Zähler vonizehnmal. Wir können erwarten, dass ein solches Konstrukt leicht mit der API vonStreamgeschrieben werden kann, und im Idealfall hätten wir einedoWhile()-Methode in einem Stream.
Leider gibt es in einem Stream keine solche Methode, und wenn wir eine Funktionalität erreichen möchten, die der Standardschleife vondo-whileähnelt, müssen wir eine Methode vonlimit()verwenden:
Stream integers = Stream
.iterate(0, i -> i + 1);
integers
.limit(10)
.forEach(System.out::println);
Wir haben die gleiche Funktionalität wie eine imperative while-Schleife mit weniger Code erreicht, aber der Aufruf derlimit()-Funktion ist nicht so beschreibend wie wenn wir einedoWhile()-Methode für einStream-Objekt hätten.
5. Fazit
In diesem Artikel wird erläutert, wie wir mitStream API unendliche Streams erstellen können. Wenn diese zusammen mit Transformationen wielimit() – verwendet werden, können einige Szenarien leichter zu verstehen und zu implementieren sein.
Der Code, der all diese Beispiele unterstützt, befindet sich inGitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.