Willkommen zum Java 8 Stream API Tutorial. In den letzten Java-8-Beiträgen haben wir uns mit Java 8 Interface-Änderungen und Funktionalen Schnittstellen und Lambda-Ausdrücken beschäftigt. Heute werden wir uns eine der wichtigsten APIs anschauen, die in Java 8 eingeführt wurde – Java Stream.
Java 8 Stream
- Java 8 Stream
- Sammlungen und Java Stream
- Funktionale Schnittstellen im Java 8 Stream
- java.util.Optional
- java.util.Spliterator
- Java Stream Intermediate- und Terminaloperationen
- Java Stream Short-Circuit-Operationen
- Java Stream Beispiele
- Java 8 Stream-API-Beschränkungen
Java Stream
Bevor wir uns Java Stream API-Beispiele ansehen, schauen wir uns an, warum sie benötigt wurde. Angenommen, wir möchten über eine Liste von Ganzzahlen iterieren und die Summe aller Zahlen größer als 10 ermitteln. Vor Java 8 wäre der Ansatz wie folgt:
private static int sumIterator(List<Integer> list) {
Iterator<Integer> it = list.iterator();
int sum = 0;
while (it.hasNext()) {
int num = it.next();
if (num > 10) {
sum += num;
}
}
return sum;
}
Es gibt drei Hauptprobleme mit dem oben genannten Ansatz:
- Wir möchten nur die Summe der ganzen Zahlen wissen, aber wir müssen auch angeben, wie die Iteration stattfinden wird, das wird auch als externe Iteration bezeichnet, da das Client-Programm den Algorithmus zur Iteration über die Liste steuert.
- Das Programm ist sequenziell in der Natur, es gibt keine einfache Möglichkeit, dies parallel zu tun.
- Es gibt viel Code, um selbst eine einfache Aufgabe zu erledigen.
Um all diese oben genannten Mängel zu überwinden, wurde die Java 8 Stream API eingeführt. Wir können die Java Stream API verwenden, um interne Iteration zu implementieren, die besser ist, weil das Java-Framework die Iteration kontrolliert. Interne Iteration bietet verschiedene Funktionen wie sequenzielle und parallele Ausführung, Filterung basierend auf den gegebenen Kriterien, Mapping usw. Die meisten Argumente der Java 8 Stream API-Methoden sind funktionale Schnittstellen, so dass Lambda-Ausdrücke sehr gut mit ihnen funktionieren. Sehen wir uns an, wie wir die oben genannte Logik in einer einzigen Zeile mit Java Streams schreiben können.
private static int sumStream(List<Integer> list) {
return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}
Beachten Sie, dass das obige Programm die Iterationsstrategie, Filterung und Mapping-Methoden des Java-Frameworks verwendet und die Effizienz erhöht. Zuerst werden wir uns die Kernkonzepte der Java 8 Stream API ansehen und dann werden wir einige Beispiele durchgehen, um die am häufigsten verwendeten Methoden zu verstehen.
Sammlungen und Java Stream
A collection is an in-memory data structure to hold values and before we start using collection, all the values should have been populated. Whereas a java Stream is a data structure that is computed on-demand. Java Stream doesn’t store data, it operates on the source data structure (collection and array) and produce pipelined data that we can use and perform specific operations. Such as we can create a stream from the list and filter it based on a condition. Java Stream operations use functional interfaces, that makes it a very good fit for functional programming using lambda expression. As you can see in the above example that using lambda expressions make our code readable and short. Java 8 Stream internal iteration principle helps in achieving lazy-seeking in some of the stream operations. For example filtering, mapping, or duplicate removal can be implemented lazily, allowing higher performance and scope for optimization. Java Streams are consumable, so there is no way to create a reference to stream for future usage. Since the data is on-demand, it’s not possible to reuse the same stream multiple times. Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections. All the Java Stream API interfaces and classes are in the java.util.stream
package. Since we can use primitive data types such as int, long in the collections using auto-boxing and these operations could take a lot of time, there are specific classes for primitive types – IntStream
, LongStream
and DoubleStream
.
Funktionale Schnittstellen in Java 8 Stream
Einige der häufig verwendeten funktionalen Schnittstellen in den Java 8 Stream-API-Methoden sind:
- Funktion und BiFunction: Funktion repräsentiert eine Funktion, die ein Argument eines Typs annimmt und ein anderes Argument eines Typs zurückgibt.
Function<T, R>
ist die generische Form, wobei T der Typ der Eingabe für die Funktion ist und R der Typ des Ergebnisses der Funktion ist. Zur Behandlung von primitiven Typen gibt es spezifische Funktionsschnittstellen –ToIntFunction
,ToLongFunction
,ToDoubleFunction
,ToIntBiFunction
,ToLongBiFunction
,ToDoubleBiFunction
,LongToIntFunction
,LongToDoubleFunction
,IntToLongFunction
,IntToDoubleFunction
usw. Einige der Stream-Methoden, in denenFunction
oder ihre primitive Spezialisierung verwendet wird, sind:- <R> Stream<R> map(Function<? super T, ? extends R> mapper)
- IntStream mapToInt(ToIntFunction<? super T> mapper) – ähnlich für long und double, die primitive spezifische Stream zurückgeben.
- IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper) – ähnlich für long und double
- <A> A[] toArray(IntFunction<A[]> generator)
- <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
- Predicate und BiPredicate: Es stellt ein Prädikat dar, gegen das Elemente des Streams getestet werden. Dies wird verwendet, um Elemente aus dem Java-Stream zu filtern. Genauso wie
Function
gibt es primitive spezifische Schnittstellen für int, long und double. Einige der Stream-Methoden, bei denenPredicate
oderBiPredicate
Spezialisierungen verwendet werden, sind:- Stream<T> filter(Predicate<? super T> predicate)
- boolean anyMatch(Predicate<? super T> predicate)
- boolean allMatch(Predicate<? super T> predicate)
- boolean noneMatch(Predicate<? super T> predicate)
- Verbraucher und BiVerbraucher: Es repräsentiert eine Operation, die ein einzelnes Eingabeargument akzeptiert und kein Ergebnis zurückgibt. Es kann verwendet werden, um eine Aktion auf allen Elementen des Java-Streams auszuführen. Einige der Java 8 Stream-Methoden, bei denen
Verbraucher
,BiVerbraucher
oder deren primitiven Spezialisierungsschnittstellen verwendet werden, sind:- Stream<T> peek(Consumer<? super T> action)
- void forEach(Consumer<? super T> action)
- void forEachOrdered(Consumer<? super T> action)
- Lieferant: Lieferant repräsentiert eine Operation, durch die wir neue Werte im Stream generieren können. Einige der Methoden im Stream, die ein
Lieferant
-Argument akzeptieren, sind:- public static<T> Stream<T> generate(Supplier<T> s)
- <R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)
java.util.Optional
Java Optional ist ein Container-Objekt, das einen nicht-null-Wert enthalten kann oder auch nicht. Wenn ein Wert vorhanden ist, gibt isPresent()
true zurück und get()
liefert den Wert zurück. Terminale Operationen von Streams geben ein Optional-Objekt zurück. Einige dieser Methoden sind:
- Optional<T> reduce(BinaryOperator<T> accumulator)
- Optional<T> min(Comparator<? super T> comparator)
- Optional<T> max(Comparator<? super T> comparator)
- Optional<T> findFirst()
- Optional<T> findAny()
java.util.Spliterator
Für die Unterstützung der parallelen Ausführung in der Java 8 Stream-API wird das Spliterator
-Interface verwendet. Die Methode trySplit
von Spliterator gibt einen neuen Spliterator zurück, der eine Teilmenge der Elemente des originalen Spliterator verwaltet.
Java Stream Intermediate und Terminal Operations
Java Stream API-Operationen, die einen neuen Stream zurückgeben, werden als Zwischenoperationen bezeichnet. Meistens sind diese Operationen träge, sodass sie neue Stream-Elemente erzeugen und an die nächste Operation senden. Zwischenoperationen sind niemals abschließende ergebnisproduzierende Operationen. Häufig verwendete Zwischenoperationen sind filter
und map
. Java 8 Stream API-Operationen, die ein Ergebnis zurückgeben oder eine Seitenauswirkung erzeugen. Sobald eine Terminalmethode auf einem Stream aufgerufen wird, verbraucht sie den Stream, und danach können wir den Stream nicht mehr verwenden. Terminaloperationen sind träge, d. h. sie verarbeiten alle Elemente im Stream, bevor sie das Ergebnis zurückgeben. Häufig verwendete Terminalmethoden sind forEach
, toArray
, min
, max
, findFirst
, anyMatch
, allMatch
usw. Sie können Terminalmethoden anhand des Rückgabetyps identifizieren, da sie niemals einen Stream zurückgeben.
Java Stream Short-Circuiting-Operationen
Eine Zwischenoperation wird als „short-circuiting“ bezeichnet, wenn sie für einen unendlichen Stream einen endlichen Stream erzeugen kann. Zum Beispiel sind limit()
und skip()
zwei „short-circuiting“ Zwischenoperationen. Eine Terminaloperation wird als „short-circuiting“ bezeichnet, wenn sie in endlicher Zeit für einen unendlichen Stream enden kann. Zum Beispiel sind anyMatch
, allMatch
, noneMatch
, findFirst
und findAny
„short-circuiting“ Terminaloperationen.
Java Stream Beispiele
I have covered almost all the important parts of the Java 8 Stream API. It’s exciting to use this new API features and let’s see it in action with some java stream examples.
Erstellen von Java Streams
Es gibt mehrere Möglichkeiten, wie wir einen Java-Stream aus Arrays und Sammlungen erstellen können. Schauen wir uns das anhand einfacher Beispiele an.
-
Wir können
Stream.of()
verwenden, um einen Stream aus ähnlichen Datentypen zu erstellen. Zum Beispiel können wir einen Java-Stream von Ganzzahlen aus einer Gruppe von int- oder Integer-Objekten erstellen.Stream<Integer> stream = Stream.of(1,2,3,4);
-
Wir können
Stream.of()
mit einem Array von Objekten verwenden, um den Stream zurückzugeben. Beachten Sie, dass es keine Autoboxing unterstützt, daher können wir kein Array vom primitiven Typ übergeben.Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); //funktioniert einwandfrei Stream<Integer> stream1 = Stream.of(new int[]{1,2,3,4}); //Compile-Zeitfehler, Typenkonflikt: Kann nicht von Stream<int[]> nach Stream<Integer> konvertiert werden
-
Mit der Methode
stream()
können wir einen sequenziellen Stream erstellen und mitparallelStream()
einen parallelen Stream.List
myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); //sequenzieller Stream Stream sequenziellerStream = myList.stream(); //paralleler Stream Stream parallelerStream = myList.parallelStream(); -
Mit den Methoden
Stream.generate()
undStream.iterate()
können wir einen Stream erstellen.Stream
stream1 = Stream.generate(() -> {return "abc";}); Stream stream2 = Stream.iterate("abc", (i) -> i); -
Mit den Methoden
Arrays.stream()
undString.chars()
.LongStream is = Arrays.stream(new long[]{1,2,3,4}); IntStream is2 = "abc".chars();
Java Stream in Collection oder Array umwandeln
Es gibt mehrere Möglichkeiten, um aus einem Java Stream eine Collection oder ein Array zu erhalten.
-
Mit der Java Stream
collect()
-Methode können wir eine Liste, eine Map oder ein Set aus einem Stream erhalten.Stream<Integer> intStream = Stream.of(1,2,3,4); List<Integer> intList = intStream.collect(Collectors.toList()); System.out.println(intList); //gibt [1, 2, 3, 4] aus intStream = Stream.of(1,2,3,4); //der Stream ist geschlossen, daher müssen wir ihn erneut erstellen Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10)); System.out.println(intMap); //gibt {1=11, 2=12, 3=13, 4=14} aus
-
Mit der
toArray()
-Methode des Streams können wir ein Array aus dem Stream erstellen.Stream<Integer> intStream = Stream.of(1,2,3,4); Integer[] intArray = intStream.toArray(Integer[]::new); System.out.println(Arrays.toString(intArray)); //gibt [1, 2, 3, 4] aus
Java Stream Zwischenoperationen
Lassen Sie uns ein Beispiel für häufig verwendete Java Stream Zwischenoperationen betrachten.
-
Stream filter() Beispiel: Wir können die filter() Methode verwenden, um Stream-Elemente auf eine Bedingung zu überprüfen und eine gefilterte Liste zu generieren.
List<Integer> myList = new ArrayList<>(); for(int i=0; i<100; i++) myList.add(i); Stream<Integer> sequentialStream = myList.stream(); Stream<Integer> highNums = sequentialStream.filter(p -> p > 90); //filtert Zahlen größer als 90 System.out.print("Hohe Zahlen größer als 90="); highNums.forEach(p -> System.out.print(p+" ")); //gibt "Hohe Zahlen größer als 90=91 92 93 94 95 96 97 98 99 " aus
-
Stream map() Beispiel: Wir können map() verwenden, um Funktionen auf einen Stream anzuwenden. Schauen wir uns an, wie wir es verwenden können, um eine Liste von Strings der Großschreibungsfunktion zu unterziehen.
Stream<String> names = Stream.of("aBc", "d", "ef"); System.out.println(names.map(s -> { return s.toUpperCase(); }).collect(Collectors.toList())); //gibt [ABC, D, EF] aus
-
Beispiel für sortierte() Stream: Wir können sorted() verwenden, um die Stream-Elemente durch Übergeben des Comparator-Arguments zu sortieren.
Stream<String> namen2 = Stream.of("aBc", "d", "ef", "123456"); List<String> reverseSortiert = namen2.sorted(Comparator.reverseOrder()).collect(Collectors.toList()); System.out.println(reverseSortiert); // [ef, d, aBc, 123456] Stream<String> namen3 = Stream.of("aBc", "d", "ef", "123456"); List<String> natuerlichSortiert = namen3.sorted().collect(Collectors.toList()); System.out.println(natuerlichSortiert); //[123456, aBc, d, ef]
-
Beispiel für flatMap() Stream: Wir können flatMap() verwenden, um einen Stream aus dem Stream der Liste zu erstellen. Sehen wir uns ein einfaches Beispiel an, um diesen Zweifel zu klären.
Stream<List<String>> urspruenglicheNamenListe = Stream.of( Arrays.asList("Pankaj"), Arrays.asList("David", "Lisa"), Arrays.asList("Amit")); // Den Stream von List
in einen String-Stream umwandeln Stream<String> flacherStream = urspruenglicheNamenListe .flatMap(strList -> strList.stream()); flacherStream.forEach(System.out::println);
Java Stream Terminal Operationen
Werfen wir einen Blick auf einige Beispiele für Java-Stream-Terminaloperationen.
-
Beispiel für Stream reduce(): Wir können reduce() verwenden, um eine Reduktion der Elemente des Streams mithilfe einer assoziativen Akkumulationsfunktion durchzuführen und ein Optional zurückzugeben. Sehen wir uns an, wie wir damit die Ganzzahlen in einem Stream multiplizieren können.
Stream<Integer> zahlen = Stream.of(1,2,3,4,5); Optional<Integer> intOptional = zahlen.reduce((i,j) -> {return i*j;}); if(intOptional.isPresent()) System.out.println("Multiplikation = "+intOptional.get()); //120
-
Beispiel für Stream count(): Wir können diese Terminaloperation verwenden, um die Anzahl der Elemente im Stream zu zählen.
Stream<Integer> zahlen1 = Stream.of(1,2,3,4,5); System.out.println("Anzahl der Elemente im Stream="+zahlen1.count()); //5
-
Stream forEach() Beispiel: Dies kann zum Iterieren über den Stream verwendet werden. Wir können dies anstelle des Iterators verwenden. Schauen wir uns an, wie man es zum Drucken aller Elemente des Streams verwendet.
Stream<Integer> numbers2 = Stream.of(1,2,3,4,5); numbers2.forEach(i -> System.out.print(i+",")); //1,2,3,4,5,
-
Stream match() Beispiele: Schauen wir uns einige Beispiele für Übereinstimmungsmethoden in der Stream-API an.
Stream<Integer> numbers3 = Stream.of(1,2,3,4,5); System.out.println("Enthält der Stream die 4? "+numbers3.anyMatch(i -> i==4)); //Enthält der Stream die 4? true Stream<Integer> numbers4 = Stream.of(1,2,3,4,5); System.out.println("Enthält der Stream alle Elemente unter 10? "+numbers4.allMatch(i -> i<10)); //Enthält der Stream alle Elemente unter 10? true Stream<Integer> numbers5 = Stream.of(1,2,3,4,5); System.out.println("Enthält der Stream nicht die 10? "+numbers5.noneMatch(i -> i==10)); //Enthält der Stream nicht die 10? true
-
Stream findFirst() Beispiel: Dies ist eine kurzschließende Terminaloperation. Schauen wir uns an, wie wir sie verwenden können, um den ersten String aus einem Stream zu finden, der mit D beginnt.
Stream<String> names4 = Stream.of("Pankaj","Amit","David", "Lisa"); Optional<String> firstNameWithD = names4.filter(i -> i.startsWith("D")).findFirst(); if(firstNameWithD.isPresent()){ System.out.println("Erster Name, der mit D beginnt="+firstNameWithD.get()); //David }
Java 8 Stream API-Beschränkungen
Die Java 8 Stream-API bringt viele neue Funktionen für die Arbeit mit Listen und Arrays mit sich, hat jedoch auch einige Einschränkungen.
-
Zustandslose Lambda-Ausdrücke: Wenn Sie Parallelstreams verwenden und Lambda-Ausdrücke zustandsbehaftet sind, kann dies zu zufälligen Antworten führen. Lassen Sie uns dies anhand eines einfachen Programms sehen.
StatefulParallelStream.java
paket com.journaldev.java8.stream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class StatefulParallelStream { public static void main(String[] args) { List<Integer> ss = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); List<Integer> result = new ArrayList<Integer>(); Stream<Integer> stream = ss.parallelStream(); stream.map(s -> { synchronized (result) { if (result.size() < 10) { result.add(s); } } return s; }).forEach( e -> {}); System.out.println(result); } }
Wenn wir das obige Programm ausführen, erhalten wir unterschiedliche Ergebnisse, da dies davon abhängt, wie der Stream iteriert wird und wir keine Reihenfolge für die parallele Verarbeitung definiert haben. Wenn wir sequentiellen Stream verwenden, wird dieses Problem nicht auftreten.
-
Einmal ein Stream verbraucht ist, kann er später nicht mehr verwendet werden. Wie Sie in den obigen Beispielen sehen können, erstelle ich jedes Mal einen Stream.
-
Es gibt viele Methoden in der Stream-API, und der verwirrendste Teil sind die überladenen Methoden. Dies macht den Lernprozess zeitaufwändig.
Das ist alles für das Java-8-Stream-Beispiel-Tutorial. Ich freue mich darauf, diese Funktion zu verwenden und den Code durch parallele Verarbeitung lesbarer und leistungsfähiger zu machen. Referenz: Java Stream API Doc
Source:
https://www.digitalocean.com/community/tutorials/java-8-stream