Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Leer Collect() Het Verzamelen van Stream-Elementen in een Collectie | Terminale Operaties in de Stream API
Stream-API

bookCollect() Het Verzamelen van Stream-Elementen in een Collectie

Je bent al bekend met terminale operaties en hebt deze zelfs gebruikt in eerdere voorbeelden en oefeningen. Nu is het tijd om nader te bekijken hoe ze werken. Als eerste behandelen we de collect()-methode, een van de belangrijkste terminale operaties in de Stream API.

De collect()-methode

Het is een van de meest krachtige hulpmiddelen bij het werken met streams, waarmee resultaten kunnen worden verzameld in een List, Set of Map, evenals het uitvoeren van complexe groeperingen en statistische berekeningen.

Er zijn twee implementaties van de collect()-methode—laten we beide verkennen.

Gebruik van collect() met functionele interfaces

De methode collect() in de Stream API kan worden gebruikt met drie functionele interfaces om volledige controle te bieden over gegevensverzameling:

  • Supplier<R> supplier – maakt een lege collectie (R) waarin elementen worden opgeslagen. Bijvoorbeeld, ArrayList::new initialiseert een nieuwe lijst;
  • BiConsumer<R, ? super T> accumulator – voegt stream-elementen (T) toe aan de collectie (R). Bijvoorbeeld, List::add voegt items toe aan een lijst;
  • BiConsumer<R, R> combiner – voegt twee collecties samen wanneer parallelle verwerking wordt gebruikt. Bijvoorbeeld, List::addAll combineert lijsten tot één.

Alle drie componenten werken samen om flexibiliteit te bieden bij gegevensverzameling. Eerst maakt de supplier een lege collectie die wordt gebruikt om elementen uit de stream te verzamelen. Vervolgens voegt de accumulator elk element toe terwijl de stream deze verwerkt. Deze stroom blijft eenvoudig in een sequentiële stream.

Bij het werken met parallelle streams (parallelStream()), wordt het echter complexer.

De gegevensverwerking wordt verdeeld over meerdere threads, waarbij elke thread een aparte collectie aanmaakt. Zodra de verwerking is voltooid, moeten deze individuele collecties worden samengevoegd tot één eindresultaat. Hier komt de combiner in beeld, die efficiënt de aparte delen samenvoegt tot één geünificeerde collectie.

Praktisch Voorbeeld

Je werkt voor een online winkel en hebt een lijst met producten. Je taak is om alleen de producten te verzamelen die meer dan $500 kosten met behulp van de collect()-methode met drie parameters.

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 + ")"; } }

De methode collect() neemt drie argumenten, die elk een andere stap definiëren bij het verzamelen van elementen in een lijst:

  • ArrayList::new (Supplier) → maakt een lege ArrayList<Product> aan om de resultaten op te slaan;

  • (list, product) -> list.add(product) (BiConsumer) → voegt elk Product toe aan de lijst als het voldoet aan de filtervoorwaarde (price > 500);

  • ArrayList::addAll (BiConsumer) → voegt meerdere lijsten samen bij gebruik van parallelle streams, zodat alle gefilterde producten worden gecombineerd in één enkele lijst.

Hoewel de derde parameter voornamelijk bedoeld is voor parallelle verwerking, is deze vereist door collect().

Gebruik van collect() met de Collector-interface

Naast het werken met drie functionele interfaces, kan de methode collect() in de Stream API ook worden gebruikt met vooraf gedefinieerde implementaties van de Collector-interface.

Deze benadering is flexibeler en handiger omdat het ingebouwde methoden biedt voor het werken met collecties.

De Collector<T, A, R>-interface bestaat uit verschillende belangrijke methoden:

  • Supplier<A> supplier() – maakt een lege container voor het verzamelen van elementen;
  • BiConsumer<A, T> accumulator() – definieert hoe elementen aan de container worden toegevoegd;
  • BinaryOperator<A> combiner() – voegt twee containers samen bij gebruik van parallelle verwerking;
  • Function<A, R> finisher() – zet de container om in het eindresultaat.

Zoals je kunt zien, lijkt deze structuur op de collect()-methode die werkt met functionele interfaces, maar introduceert het de finisher()-methode. Deze extra stap maakt verdere verwerking van de verzamelde gegevens mogelijk voordat het eindresultaat wordt teruggegeven—bijvoorbeeld het sorteren van de lijst voordat deze wordt teruggegeven.

Daarnaast biedt de Collector-interface de characteristics()-methode, die eigenschappen definieert die helpen bij het optimaliseren van de stream-uitvoering:

Deze eigenschappen helpen de Stream API om de prestaties te optimaliseren. Bijvoorbeeld, als een collectie van nature ongeordend is, kan het specificeren van UNORDERED onnodig sorteren voorkomen, waardoor de bewerking efficiënter wordt.

Praktisch Voorbeeld

Stel je voor dat je een online winkel runt en productprijzen moet verwerken voordat je ze verzamelt. Je wilt bijvoorbeeld elke prijs afronden op het dichtstbijzijnde hele getal, duplicaten verwijderen en de uiteindelijke lijst sorteren.

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

De gegevensverwerking begint door deze door te geven aan een aangepaste Collector genaamd RoundedSortedCollector.

Deze collector verzamelt eerst alle prijzen in een Set<Integer>, waardoor duplicaten automatisch worden verwijderd. Voor het toevoegen van elke waarde wordt de prijs afgerond met Math.round(price) en omgezet naar een int. Bijvoorbeeld, zowel 1200.99 als 1200.49 worden 1200, terwijl 199.99 wordt afgerond naar 200.

Als de stream in parallelle modus wordt uitgevoerd, voegt de methode combiner() twee sets samen door alle elementen van de ene set aan de andere toe te voegen. Deze stap is essentieel voor multithreaded omgevingen.

In de laatste fase, nadat alle prijzen zijn verzameld, zet de methode finisher() de set om in een gesorteerde lijst. Hierbij wordt de Set<Integer> omgezet naar een stream, wordt sorted() toegepast om de waarden in oplopende volgorde te rangschikken, en worden ze vervolgens verzameld in een List<Integer>.

Het resultaat is een gesorteerde lijst van unieke, afgeronde prijzen die gebruikt kunnen worden voor verdere berekeningen of weergavedoeleinden.

1. Wat doet de methode collect() in de Stream API?

2. Welke extra functionaliteit biedt de interface Collector ten opzichte van collect() met functionele interfaces?

question mark

Wat doet de methode collect() in de Stream API?

Select the correct answer

question mark

Welke extra functionaliteit biedt de interface Collector ten opzichte van collect() met functionele interfaces?

Select the correct answer

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 3. Hoofdstuk 1

Vraag AI

expand

Vraag AI

ChatGPT

Vraag wat u wilt of probeer een van de voorgestelde vragen om onze chat te starten.

Awesome!

Completion rate improved to 2.33

bookCollect() Het Verzamelen van Stream-Elementen in een Collectie

Veeg om het menu te tonen

Je bent al bekend met terminale operaties en hebt deze zelfs gebruikt in eerdere voorbeelden en oefeningen. Nu is het tijd om nader te bekijken hoe ze werken. Als eerste behandelen we de collect()-methode, een van de belangrijkste terminale operaties in de Stream API.

De collect()-methode

Het is een van de meest krachtige hulpmiddelen bij het werken met streams, waarmee resultaten kunnen worden verzameld in een List, Set of Map, evenals het uitvoeren van complexe groeperingen en statistische berekeningen.

Er zijn twee implementaties van de collect()-methode—laten we beide verkennen.

Gebruik van collect() met functionele interfaces

De methode collect() in de Stream API kan worden gebruikt met drie functionele interfaces om volledige controle te bieden over gegevensverzameling:

  • Supplier<R> supplier – maakt een lege collectie (R) waarin elementen worden opgeslagen. Bijvoorbeeld, ArrayList::new initialiseert een nieuwe lijst;
  • BiConsumer<R, ? super T> accumulator – voegt stream-elementen (T) toe aan de collectie (R). Bijvoorbeeld, List::add voegt items toe aan een lijst;
  • BiConsumer<R, R> combiner – voegt twee collecties samen wanneer parallelle verwerking wordt gebruikt. Bijvoorbeeld, List::addAll combineert lijsten tot één.

Alle drie componenten werken samen om flexibiliteit te bieden bij gegevensverzameling. Eerst maakt de supplier een lege collectie die wordt gebruikt om elementen uit de stream te verzamelen. Vervolgens voegt de accumulator elk element toe terwijl de stream deze verwerkt. Deze stroom blijft eenvoudig in een sequentiële stream.

Bij het werken met parallelle streams (parallelStream()), wordt het echter complexer.

De gegevensverwerking wordt verdeeld over meerdere threads, waarbij elke thread een aparte collectie aanmaakt. Zodra de verwerking is voltooid, moeten deze individuele collecties worden samengevoegd tot één eindresultaat. Hier komt de combiner in beeld, die efficiënt de aparte delen samenvoegt tot één geünificeerde collectie.

Praktisch Voorbeeld

Je werkt voor een online winkel en hebt een lijst met producten. Je taak is om alleen de producten te verzamelen die meer dan $500 kosten met behulp van de collect()-methode met drie parameters.

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 + ")"; } }

De methode collect() neemt drie argumenten, die elk een andere stap definiëren bij het verzamelen van elementen in een lijst:

  • ArrayList::new (Supplier) → maakt een lege ArrayList<Product> aan om de resultaten op te slaan;

  • (list, product) -> list.add(product) (BiConsumer) → voegt elk Product toe aan de lijst als het voldoet aan de filtervoorwaarde (price > 500);

  • ArrayList::addAll (BiConsumer) → voegt meerdere lijsten samen bij gebruik van parallelle streams, zodat alle gefilterde producten worden gecombineerd in één enkele lijst.

Hoewel de derde parameter voornamelijk bedoeld is voor parallelle verwerking, is deze vereist door collect().

Gebruik van collect() met de Collector-interface

Naast het werken met drie functionele interfaces, kan de methode collect() in de Stream API ook worden gebruikt met vooraf gedefinieerde implementaties van de Collector-interface.

Deze benadering is flexibeler en handiger omdat het ingebouwde methoden biedt voor het werken met collecties.

De Collector<T, A, R>-interface bestaat uit verschillende belangrijke methoden:

  • Supplier<A> supplier() – maakt een lege container voor het verzamelen van elementen;
  • BiConsumer<A, T> accumulator() – definieert hoe elementen aan de container worden toegevoegd;
  • BinaryOperator<A> combiner() – voegt twee containers samen bij gebruik van parallelle verwerking;
  • Function<A, R> finisher() – zet de container om in het eindresultaat.

Zoals je kunt zien, lijkt deze structuur op de collect()-methode die werkt met functionele interfaces, maar introduceert het de finisher()-methode. Deze extra stap maakt verdere verwerking van de verzamelde gegevens mogelijk voordat het eindresultaat wordt teruggegeven—bijvoorbeeld het sorteren van de lijst voordat deze wordt teruggegeven.

Daarnaast biedt de Collector-interface de characteristics()-methode, die eigenschappen definieert die helpen bij het optimaliseren van de stream-uitvoering:

Deze eigenschappen helpen de Stream API om de prestaties te optimaliseren. Bijvoorbeeld, als een collectie van nature ongeordend is, kan het specificeren van UNORDERED onnodig sorteren voorkomen, waardoor de bewerking efficiënter wordt.

Praktisch Voorbeeld

Stel je voor dat je een online winkel runt en productprijzen moet verwerken voordat je ze verzamelt. Je wilt bijvoorbeeld elke prijs afronden op het dichtstbijzijnde hele getal, duplicaten verwijderen en de uiteindelijke lijst sorteren.

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

De gegevensverwerking begint door deze door te geven aan een aangepaste Collector genaamd RoundedSortedCollector.

Deze collector verzamelt eerst alle prijzen in een Set<Integer>, waardoor duplicaten automatisch worden verwijderd. Voor het toevoegen van elke waarde wordt de prijs afgerond met Math.round(price) en omgezet naar een int. Bijvoorbeeld, zowel 1200.99 als 1200.49 worden 1200, terwijl 199.99 wordt afgerond naar 200.

Als de stream in parallelle modus wordt uitgevoerd, voegt de methode combiner() twee sets samen door alle elementen van de ene set aan de andere toe te voegen. Deze stap is essentieel voor multithreaded omgevingen.

In de laatste fase, nadat alle prijzen zijn verzameld, zet de methode finisher() de set om in een gesorteerde lijst. Hierbij wordt de Set<Integer> omgezet naar een stream, wordt sorted() toegepast om de waarden in oplopende volgorde te rangschikken, en worden ze vervolgens verzameld in een List<Integer>.

Het resultaat is een gesorteerde lijst van unieke, afgeronde prijzen die gebruikt kunnen worden voor verdere berekeningen of weergavedoeleinden.

1. Wat doet de methode collect() in de Stream API?

2. Welke extra functionaliteit biedt de interface Collector ten opzichte van collect() met functionele interfaces?

question mark

Wat doet de methode collect() in de Stream API?

Select the correct answer

question mark

Welke extra functionaliteit biedt de interface Collector ten opzichte van collect() met functionele interfaces?

Select the correct answer

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 3. Hoofdstuk 1
some-alt