Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Lernen Collect() Sammeln von Stream-Elementen in Einer Collection | Terminaloperationen in der Stream-API
Stream-API

bookCollect() Sammeln von Stream-Elementen in Einer Collection

Sie sind bereits mit terminalen Operationen vertraut und haben diese sogar in früheren Beispielen und Übungen verwendet. Nun ist es an der Zeit, einen genaueren Blick darauf zu werfen, wie sie funktionieren. Zuerst betrachten wir die Methode collect(), eine der wichtigsten terminalen Operationen in der Stream-API.

Die collect()-Methode

Sie ist eines der leistungsstärksten Werkzeuge bei der Arbeit mit Streams und ermöglicht das Sammeln von Ergebnissen in einer List, Set oder Map sowie das Durchführen komplexer Gruppierungen und statistischer Berechnungen.

Es gibt zwei Implementierungen der Methode collect()—lassen Sie uns beide untersuchen.

Verwendung von collect() mit funktionalen Schnittstellen

Die Methode collect() im Stream API kann mit drei funktionalen Schnittstellen verwendet werden, um die Datensammlung vollständig zu steuern:

  • Supplier<R> supplier – erstellt eine leere Sammlung (R), in der Elemente gespeichert werden. Zum Beispiel initialisiert ArrayList::new eine neue Liste;
  • BiConsumer<R, ? super T> accumulator – fügt Streamelemente (T) zur Sammlung (R) hinzu. Beispielsweise fügt List::add Elemente zu einer Liste hinzu;
  • BiConsumer<R, R> combiner – verbindet zwei Sammlungen, wenn parallele Verarbeitung verwendet wird. Zum Beispiel kombiniert List::addAll Listen zu einer einzigen.

Alle drei Komponenten arbeiten zusammen, um Flexibilität bei der Datensammlung zu bieten. Zuerst erstellt der supplier eine leere Sammlung, die zum Sammeln der Elemente aus dem Stream verwendet wird. Anschließend fügt der accumulator jedes Element hinzu, während der Stream sie verarbeitet. Dieser Ablauf bleibt bei einem sequentiellen Stream unkompliziert.

Bei der Arbeit mit parallelen Streams (parallelStream()) wird der Prozess jedoch komplexer.

Die Datenverarbeitung erfolgt über mehrere Threads, wobei jeder Thread eine eigene separate Sammlung erstellt. Nach Abschluss der Verarbeitung müssen diese einzelnen Sammlungen zu einem einzigen Ergebnis zusammengeführt werden. Hier kommt der combiner ins Spiel, der die separaten Teile effizient zu einer einheitlichen Sammlung kombiniert.

Praktisches Beispiel

Sie arbeiten für einen Online-Shop und haben eine Liste von Produkten. Ihre Aufgabe ist es, nur die Produkte zu sammeln, die mehr als $500 kosten, indem Sie die Methode collect() mit drei Parametern verwenden.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { // Initial list of products List<Product> productList = List.of( new Product("Laptop", 1200.99), new Product("Phone", 599.49), new Product("Headphones", 199.99), new Product("Monitor", 299.99), new Product("Tablet", 699.99) ); // Filtering and collecting products over $500 using `collect()` List<Product> expensiveProducts = productList.parallelStream() .filter(product -> product.getPrice() > 500) // Keep only expensive products .collect( ArrayList::new, // Create a new list (list, product) -> list.add(product), // Add each product to the list ArrayList::addAll // Merge lists (if the stream is parallel) ); // Print the result System.out.print("Products over $500: " + expensiveProducts); } } class Product { private String name; private double price; Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } @Override public String toString() { return name + " ($" + price + ")"; } }

Die Methode collect() nimmt drei Argumente entgegen, die jeweils einen anderen Schritt beim Sammeln von Elementen in eine Liste definieren:

  • ArrayList::new (Supplier) → erstellt eine leere ArrayList<Product>, um die Ergebnisse zu speichern;

  • (list, product) -> list.add(product) (BiConsumer) → fügt jedes Product der Liste hinzu, sofern es die Filterbedingung (price > 500) erfüllt;

  • ArrayList::addAll (BiConsumer) → führt mehrere Listen zusammen, wenn parallele Streams verwendet werden, sodass alle gefilterten Produkte in einer einzigen Liste kombiniert werden.

Obwohl der dritte Parameter hauptsächlich für die parallele Verarbeitung vorgesehen ist, ist er für collect() erforderlich.

Verwendung von collect() mit dem Collector-Interface

Neben der Arbeit mit drei funktionalen Interfaces kann die Methode collect() im Stream API auch mit vordefinierten Implementierungen des Collector-Interfaces verwendet werden.

Dieser Ansatz ist flexibler und komfortabler, da er eingebaute Methoden für die Arbeit mit Collections bereitstellt.

Das Collector<T, A, R>-Interface besteht aus mehreren Schlüsselmethode:

  • Supplier<A> supplier() – erstellt einen leeren Container zum Sammeln der Elemente;
  • BiConsumer<A, T> accumulator() – definiert, wie Elemente zum Container hinzugefügt werden;
  • BinaryOperator<A> combiner() – führt zwei Container zusammen, wenn parallele Verarbeitung verwendet wird;
  • Function<A, R> finisher() – wandelt den Container in das Endergebnis um.

Wie ersichtlich, ist diese Struktur der collect()-Methode ähnlich, die mit funktionalen Schnittstellen arbeitet, führt jedoch die Methode finisher() ein. Dieser zusätzliche Schritt ermöglicht eine weitere Verarbeitung der gesammelten Daten, bevor das Endergebnis zurückgegeben wird—zum Beispiel das Sortieren der Liste vor der Rückgabe.

Zusätzlich stellt das Collector-Interface die Methode characteristics() bereit, die Eigenschaften definiert, welche die Optimierung der Stream-Ausführung unterstützen:

Diese Eigenschaften helfen der Stream-API, die Leistung zu optimieren. Wenn eine Sammlung beispielsweise von Natur aus ungeordnet ist, kann die Angabe von UNORDERED unnötiges Sortieren verhindern und die Operation somit effizienter gestalten.

Praktisches Beispiel

Angenommen, Sie betreiben einen Online-Shop und müssen Produktpreise vor dem Sammeln verarbeiten. Beispielsweise sollen alle Preise auf die nächste ganze Zahl gerundet, Duplikate entfernt und die endgültige Liste sortiert werden.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
package com.example; import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<Double> prices = List.of(1200.99, 599.49, 199.99, 599.49, 1200.49, 200.0); // Using a custom `Collector` to round prices, remove duplicates, and sort List<Integer> processedPrices = prices.parallelStream() .collect(new RoundedSortedCollector()); System.out.println("Processed product prices: " + processedPrices); } } // Custom `Collector` that rounds prices, removes duplicates, and sorts them class RoundedSortedCollector implements Collector<Double, Set<Integer>, List<Integer>> { @Override public Supplier<Set<Integer>> supplier() { // Creates a `HashSet` to store unique rounded values return HashSet::new; } @Override public BiConsumer<Set<Integer>, Double> accumulator() { // Rounds price and adds to the set return (set, price) -> set.add((int) Math.round(price)); } @Override public BinaryOperator<Set<Integer>> combiner() { return (set1, set2) -> { set1.addAll(set2); // Merges two sets return set1; }; } @Override public Function<Set<Integer>, List<Integer>> finisher() { return set -> set.stream() .sorted() // Sorts the final list .toList(); } @Override public Set<Characteristics> characteristics() { // Order is not important during accumulation return Set.of(Characteristics.UNORDERED); } }

Die Datenverarbeitung beginnt mit der Übergabe an einen benutzerdefinierten Collector namens RoundedSortedCollector.

Dieser Collector sammelt zunächst alle Preise in einem Set<Integer>, wodurch Duplikate automatisch entfernt werden. Vor dem Hinzufügen jedes Werts wird der Preis mit Math.round(price) gerundet und in einen int umgewandelt. Zum Beispiel werden sowohl 1200.99 als auch 1200.49 zu 1200, während 199.99 auf 200 aufgerundet wird.

Wenn der Stream im Parallelmodus ausgeführt wird, führt die Methode combiner() zwei Mengen zusammen, indem alle Elemente einer Menge zur anderen hinzugefügt werden. Dieser Schritt ist entscheidend für mehrthreadige Umgebungen.

Im letzten Schritt, nachdem alle Preise gesammelt wurden, wandelt die Methode finisher() die Menge in eine sortierte Liste um. Das Set<Integer> wird in einen Stream umgewandelt, mit sorted() in aufsteigender Reihenfolge sortiert und anschließend in eine List<Integer> gesammelt.

Das Ergebnis ist eine sortierte Liste von einzigartigen, gerundeten Preisen, die für weitere Berechnungen oder Anzeigezwecke verwendet werden kann.

1. Was bewirkt die Methode collect() in der Stream-API?

2. Welche zusätzliche Fähigkeit bietet das Collector-Interface im Vergleich zu collect() mit funktionalen Interfaces?

question mark

Was bewirkt die Methode collect() in der Stream-API?

Select the correct answer

question mark

Welche zusätzliche Fähigkeit bietet das Collector-Interface im Vergleich zu collect() mit funktionalen Interfaces?

Select the correct answer

War alles klar?

Wie können wir es verbessern?

Danke für Ihr Feedback!

Abschnitt 3. Kapitel 1

Fragen Sie AI

expand

Fragen Sie AI

ChatGPT

Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen

Suggested prompts:

Can you explain the difference between using collect() with functional interfaces and with the Collector interface?

What are some common use cases for the collect() method in real-world applications?

Could you provide a simple code example demonstrating the use of collect() in Java?

Awesome!

Completion rate improved to 2.33

bookCollect() Sammeln von Stream-Elementen in Einer Collection

Swipe um das Menü anzuzeigen

Sie sind bereits mit terminalen Operationen vertraut und haben diese sogar in früheren Beispielen und Übungen verwendet. Nun ist es an der Zeit, einen genaueren Blick darauf zu werfen, wie sie funktionieren. Zuerst betrachten wir die Methode collect(), eine der wichtigsten terminalen Operationen in der Stream-API.

Die collect()-Methode

Sie ist eines der leistungsstärksten Werkzeuge bei der Arbeit mit Streams und ermöglicht das Sammeln von Ergebnissen in einer List, Set oder Map sowie das Durchführen komplexer Gruppierungen und statistischer Berechnungen.

Es gibt zwei Implementierungen der Methode collect()—lassen Sie uns beide untersuchen.

Verwendung von collect() mit funktionalen Schnittstellen

Die Methode collect() im Stream API kann mit drei funktionalen Schnittstellen verwendet werden, um die Datensammlung vollständig zu steuern:

  • Supplier<R> supplier – erstellt eine leere Sammlung (R), in der Elemente gespeichert werden. Zum Beispiel initialisiert ArrayList::new eine neue Liste;
  • BiConsumer<R, ? super T> accumulator – fügt Streamelemente (T) zur Sammlung (R) hinzu. Beispielsweise fügt List::add Elemente zu einer Liste hinzu;
  • BiConsumer<R, R> combiner – verbindet zwei Sammlungen, wenn parallele Verarbeitung verwendet wird. Zum Beispiel kombiniert List::addAll Listen zu einer einzigen.

Alle drei Komponenten arbeiten zusammen, um Flexibilität bei der Datensammlung zu bieten. Zuerst erstellt der supplier eine leere Sammlung, die zum Sammeln der Elemente aus dem Stream verwendet wird. Anschließend fügt der accumulator jedes Element hinzu, während der Stream sie verarbeitet. Dieser Ablauf bleibt bei einem sequentiellen Stream unkompliziert.

Bei der Arbeit mit parallelen Streams (parallelStream()) wird der Prozess jedoch komplexer.

Die Datenverarbeitung erfolgt über mehrere Threads, wobei jeder Thread eine eigene separate Sammlung erstellt. Nach Abschluss der Verarbeitung müssen diese einzelnen Sammlungen zu einem einzigen Ergebnis zusammengeführt werden. Hier kommt der combiner ins Spiel, der die separaten Teile effizient zu einer einheitlichen Sammlung kombiniert.

Praktisches Beispiel

Sie arbeiten für einen Online-Shop und haben eine Liste von Produkten. Ihre Aufgabe ist es, nur die Produkte zu sammeln, die mehr als $500 kosten, indem Sie die Methode collect() mit drei Parametern verwenden.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
package com.example; import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { // Initial list of products List<Product> productList = List.of( new Product("Laptop", 1200.99), new Product("Phone", 599.49), new Product("Headphones", 199.99), new Product("Monitor", 299.99), new Product("Tablet", 699.99) ); // Filtering and collecting products over $500 using `collect()` List<Product> expensiveProducts = productList.parallelStream() .filter(product -> product.getPrice() > 500) // Keep only expensive products .collect( ArrayList::new, // Create a new list (list, product) -> list.add(product), // Add each product to the list ArrayList::addAll // Merge lists (if the stream is parallel) ); // Print the result System.out.print("Products over $500: " + expensiveProducts); } } class Product { private String name; private double price; Product(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public double getPrice() { return price; } @Override public String toString() { return name + " ($" + price + ")"; } }

Die Methode collect() nimmt drei Argumente entgegen, die jeweils einen anderen Schritt beim Sammeln von Elementen in eine Liste definieren:

  • ArrayList::new (Supplier) → erstellt eine leere ArrayList<Product>, um die Ergebnisse zu speichern;

  • (list, product) -> list.add(product) (BiConsumer) → fügt jedes Product der Liste hinzu, sofern es die Filterbedingung (price > 500) erfüllt;

  • ArrayList::addAll (BiConsumer) → führt mehrere Listen zusammen, wenn parallele Streams verwendet werden, sodass alle gefilterten Produkte in einer einzigen Liste kombiniert werden.

Obwohl der dritte Parameter hauptsächlich für die parallele Verarbeitung vorgesehen ist, ist er für collect() erforderlich.

Verwendung von collect() mit dem Collector-Interface

Neben der Arbeit mit drei funktionalen Interfaces kann die Methode collect() im Stream API auch mit vordefinierten Implementierungen des Collector-Interfaces verwendet werden.

Dieser Ansatz ist flexibler und komfortabler, da er eingebaute Methoden für die Arbeit mit Collections bereitstellt.

Das Collector<T, A, R>-Interface besteht aus mehreren Schlüsselmethode:

  • Supplier<A> supplier() – erstellt einen leeren Container zum Sammeln der Elemente;
  • BiConsumer<A, T> accumulator() – definiert, wie Elemente zum Container hinzugefügt werden;
  • BinaryOperator<A> combiner() – führt zwei Container zusammen, wenn parallele Verarbeitung verwendet wird;
  • Function<A, R> finisher() – wandelt den Container in das Endergebnis um.

Wie ersichtlich, ist diese Struktur der collect()-Methode ähnlich, die mit funktionalen Schnittstellen arbeitet, führt jedoch die Methode finisher() ein. Dieser zusätzliche Schritt ermöglicht eine weitere Verarbeitung der gesammelten Daten, bevor das Endergebnis zurückgegeben wird—zum Beispiel das Sortieren der Liste vor der Rückgabe.

Zusätzlich stellt das Collector-Interface die Methode characteristics() bereit, die Eigenschaften definiert, welche die Optimierung der Stream-Ausführung unterstützen:

Diese Eigenschaften helfen der Stream-API, die Leistung zu optimieren. Wenn eine Sammlung beispielsweise von Natur aus ungeordnet ist, kann die Angabe von UNORDERED unnötiges Sortieren verhindern und die Operation somit effizienter gestalten.

Praktisches Beispiel

Angenommen, Sie betreiben einen Online-Shop und müssen Produktpreise vor dem Sammeln verarbeiten. Beispielsweise sollen alle Preise auf die nächste ganze Zahl gerundet, Duplikate entfernt und die endgültige Liste sortiert werden.

Main.java

Main.java

copy
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
package com.example; import java.util.*; import java.util.function.*; import java.util.stream.Collector; import java.util.stream.Stream; public class Main { public static void main(String[] args) { List<Double> prices = List.of(1200.99, 599.49, 199.99, 599.49, 1200.49, 200.0); // Using a custom `Collector` to round prices, remove duplicates, and sort List<Integer> processedPrices = prices.parallelStream() .collect(new RoundedSortedCollector()); System.out.println("Processed product prices: " + processedPrices); } } // Custom `Collector` that rounds prices, removes duplicates, and sorts them class RoundedSortedCollector implements Collector<Double, Set<Integer>, List<Integer>> { @Override public Supplier<Set<Integer>> supplier() { // Creates a `HashSet` to store unique rounded values return HashSet::new; } @Override public BiConsumer<Set<Integer>, Double> accumulator() { // Rounds price and adds to the set return (set, price) -> set.add((int) Math.round(price)); } @Override public BinaryOperator<Set<Integer>> combiner() { return (set1, set2) -> { set1.addAll(set2); // Merges two sets return set1; }; } @Override public Function<Set<Integer>, List<Integer>> finisher() { return set -> set.stream() .sorted() // Sorts the final list .toList(); } @Override public Set<Characteristics> characteristics() { // Order is not important during accumulation return Set.of(Characteristics.UNORDERED); } }

Die Datenverarbeitung beginnt mit der Übergabe an einen benutzerdefinierten Collector namens RoundedSortedCollector.

Dieser Collector sammelt zunächst alle Preise in einem Set<Integer>, wodurch Duplikate automatisch entfernt werden. Vor dem Hinzufügen jedes Werts wird der Preis mit Math.round(price) gerundet und in einen int umgewandelt. Zum Beispiel werden sowohl 1200.99 als auch 1200.49 zu 1200, während 199.99 auf 200 aufgerundet wird.

Wenn der Stream im Parallelmodus ausgeführt wird, führt die Methode combiner() zwei Mengen zusammen, indem alle Elemente einer Menge zur anderen hinzugefügt werden. Dieser Schritt ist entscheidend für mehrthreadige Umgebungen.

Im letzten Schritt, nachdem alle Preise gesammelt wurden, wandelt die Methode finisher() die Menge in eine sortierte Liste um. Das Set<Integer> wird in einen Stream umgewandelt, mit sorted() in aufsteigender Reihenfolge sortiert und anschließend in eine List<Integer> gesammelt.

Das Ergebnis ist eine sortierte Liste von einzigartigen, gerundeten Preisen, die für weitere Berechnungen oder Anzeigezwecke verwendet werden kann.

1. Was bewirkt die Methode collect() in der Stream-API?

2. Welche zusätzliche Fähigkeit bietet das Collector-Interface im Vergleich zu collect() mit funktionalen Interfaces?

question mark

Was bewirkt die Methode collect() in der Stream-API?

Select the correct answer

question mark

Welche zusätzliche Fähigkeit bietet das Collector-Interface im Vergleich zu collect() mit funktionalen Interfaces?

Select the correct answer

War alles klar?

Wie können wir es verbessern?

Danke für Ihr Feedback!

Abschnitt 3. Kapitel 1
some-alt