Collect() 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 initialisiertArrayList::neweine neue Liste;BiConsumer<R, ? super T> accumulator– fügt Streamelemente (T) zur Sammlung (R) hinzu. Beispielsweise fügtList::addElemente zu einer Liste hinzu;BiConsumer<R, R> combiner– verbindet zwei Sammlungen, wenn parallele Verarbeitung verwendet wird. Zum Beispiel kombiniertList::addAllListen 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package 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 leereArrayList<Product>, um die Ergebnisse zu speichern; -
(list, product) -> list.add(product)(BiConsumer) → fügt jedesProductder 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package 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?
Danke für Ihr Feedback!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen
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
Collect() 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 initialisiertArrayList::neweine neue Liste;BiConsumer<R, ? super T> accumulator– fügt Streamelemente (T) zur Sammlung (R) hinzu. Beispielsweise fügtList::addElemente zu einer Liste hinzu;BiConsumer<R, R> combiner– verbindet zwei Sammlungen, wenn parallele Verarbeitung verwendet wird. Zum Beispiel kombiniertList::addAllListen 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152package 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 leereArrayList<Product>, um die Ergebnisse zu speichern; -
(list, product) -> list.add(product)(BiConsumer) → fügt jedesProductder 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
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455package 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?
Danke für Ihr Feedback!