Collect(): Virran Alkioiden Kerääminen Kokoelmaan
Olet jo perehtynyt pääteoperaatioihin ja olet käyttänyt niitä aiemmissa esimerkeissä ja harjoituksissa. Nyt on aika tarkastella tarkemmin niiden toimintaa. Ensimmäisenä käsitellään collect()-metodia, joka on yksi Stream API:n keskeisistä pääteoperaatioista.
collect()-metodi
Se on yksi tehokkaimmista työkaluista virtojen käsittelyssä, mahdollistaen tulosten keräämisen esimerkiksi List-, Set- tai Map-kokoelmiin sekä monimutkaisten ryhmittelyjen ja tilastollisten laskentojen suorittamisen.
collect()-metodilla on kaksi toteutusta—tutustutaan molempiin.
collect()-metodin käyttö funktionaalisten rajapintojen kanssa
collect()-metodia Stream API:ssa voidaan käyttää kolmen funktionaalisen rajapinnan kanssa, mikä antaa täyden hallinnan datan keräämiseen:
Supplier<R> supplier– luo tyhjän kokoelman (R), johon elementit tallennetaan. EsimerkiksiArrayList::newalustaa uuden listan;BiConsumer<R, ? super T> accumulator– lisää virran elementit (T) kokoelmaan (R). EsimerkiksiList::addliittää alkiot listaan;BiConsumer<R, R> combiner– yhdistää kaksi kokoelmaa kun käytetään rinnakkaiskäsittelyä. EsimerkiksiList::addAllyhdistää listat yhdeksi.
Kaikki kolme komponenttia toimivat yhdessä tarjoten joustavuutta datan keräämiseen. Ensin supplier luo tyhjän kokoelman, jota käytetään virran elementtien keräämiseen. Tämän jälkeen accumulator lisää jokaisen elementin virran käsittelyn edetessä. Tämä prosessi pysyy suoraviivaisena sekventiaalisessa virrassa.
Kuitenkin, kun käytetään rinnakkaisvirtoja (parallelStream()), tilanne monimutkaistuu.
Tietojenkäsittely jaetaan useille säikeille, joista jokainen luo oman erillisen kokoelmansa. Kun käsittely on valmis, nämä yksittäiset kokoelmat täytyy yhdistää yhdeksi tulokseksi. Tässä vaiheessa combiner astuu kuvaan ja yhdistää tehokkaasti erilliset osat yhdeksi yhtenäiseksi kokoelmaksi.
Käytännön esimerkki
Työskentelet verkkokaupassa ja sinulla on tuotelista. Tehtävänäsi on kerätä vain ne tuotteet, joiden hinta on yli 500 $, käyttämällä collect()-metodia ja kolmea parametria.
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 + ")"; } }
Metodi collect() ottaa kolme argumenttia, joista jokainen määrittää eri vaiheen elementtien keräämisessä listaan:
-
ArrayList::new(Supplier) → luo tyhjänArrayList<Product>tulosten tallentamista varten; -
(list, product) -> list.add(product)(BiConsumer) → lisää jokaisenProduct-olion listaan, jos se täyttää suodatusehdon (price > 500); -
ArrayList::addAll(BiConsumer) → yhdistää useita listoja käytettäessä rinnakkaisvirtoja, varmistaen että kaikki suodatetut tuotteet yhdistetään yhdeksi listaksi.
Vaikka kolmas parametri on tarkoitettu pääasiassa rinnakkaiskäsittelyyn, se on collect()-metodissa pakollinen.
collect()-metodin käyttö Collector-rajapinnan kanssa
Lisäksi, että collect()-metodia voidaan käyttää kolmen funktionaalisen rajapinnan kanssa, sitä voidaan käyttää myös Collector-rajapinnan valmiiden toteutusten kanssa Stream API:ssa.
Tämä lähestymistapa on joustavampi ja käytännöllisempi, koska se tarjoaa sisäänrakennettuja metodeja kokoelmien käsittelyyn.
Collector<T, A, R> -rajapinta koostuu useista keskeisistä metodeista:
Supplier<A> supplier()– luo tyhjän säiliön elementtien keräämistä varten;BiConsumer<A, T> accumulator()– määrittää, miten elementit lisätään säiliöön;BinaryOperator<A> combiner()– yhdistää kaksi säiliötä kun käytetään rinnakkaiskäsittelyä;Function<A, R> finisher()– muuntaa säiliön lopulliseksi tulokseksi.
Kuten huomaat, tämä rakenne muistuttaa collect()-metodia, joka toimii funktionaalisten rajapintojen kanssa, mutta siihen on lisätty finisher()-metodi. Tämä lisävaihe mahdollistaa lisäkäsittelyn kerätyille tiedoille ennen kuin lopullinen tulos palautetaan—esimerkiksi listan lajittelu ennen palauttamista.
Lisäksi Collector-rajapinta tarjoaa characteristics()-metodin, joka määrittelee ominaisuudet, jotka auttavat optimoimaan streamin suorituksen:
Nämä ominaisuudet auttavat Stream APIa optimoimaan suorituskykyä. Esimerkiksi, jos kokoelma on luonteeltaan järjestämätön, UNORDERED-ominaisuuden määrittäminen voi estää tarpeettoman lajittelun, mikä tekee toiminnosta tehokkaamman.
Käytännön esimerkki
Kuvittele, että ylläpidät verkkokauppaa ja sinun täytyy käsitellä tuotteiden hintoja ennen niiden keräämistä. Esimerkiksi haluat pyöristää jokaisen hinnan lähimpään kokonaislukuun, poistaa duplikaatit ja lajitella lopullisen listan.
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); } }
Tietojen käsittely aloitetaan syöttämällä ne mukautettuun Collector-luokkaan nimeltä RoundedSortedCollector.
Tämä kerääjä kerää ensin kaikki hinnat Set<Integer>-kokoelmaan, jolloin päällekkäiset arvot poistuvat automaattisesti. Ennen arvon lisäämistä hinta pyöristetään käyttämällä Math.round(price) ja muunnetaan int-arvoksi. Esimerkiksi sekä 1200.99 että 1200.49 muuttuvat arvoksi 1200, kun taas 199.99 pyöristyy ylöspäin arvoksi 200.
Jos virta suoritetaan rinnakkaistilassa, combiner()-metodi yhdistää kaksi joukkoa lisäämällä kaikki toisen joukon alkiot toiseen. Tämä vaihe on olennainen monisäikeisissä ympäristöissä.
Viimeisessä vaiheessa, kun kaikki hinnat on kerätty, finisher()-metodi muuntaa joukon lajitelluksi listaksi. Se muuntaa Set<Integer>-joukon virraksi, käyttää sorted()-metodia arvojen järjestämiseksi nousevaan järjestykseen ja kerää ne sitten List<Integer>-listaan.
Tuloksena saadaan lajiteltu lista yksilöllisistä, pyöristetyistä hinnoista, joita voidaan käyttää jatko-laskentaan tai näyttötarkoituksiin.
1. Mitä collect()-metodi tekee Stream API:ssa?
2. Mitä lisäominaisuutta Collector-rajapinta tarjoaa verrattuna collect()-metodin käyttöön funktionaalisten rajapintojen kanssa?
Kiitos palautteestasi!
Kysy tekoälyä
Kysy tekoälyä
Kysy mitä tahansa tai kokeile jotakin ehdotetuista kysymyksistä aloittaaksesi keskustelumme
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(): Virran Alkioiden Kerääminen Kokoelmaan
Pyyhkäise näyttääksesi valikon
Olet jo perehtynyt pääteoperaatioihin ja olet käyttänyt niitä aiemmissa esimerkeissä ja harjoituksissa. Nyt on aika tarkastella tarkemmin niiden toimintaa. Ensimmäisenä käsitellään collect()-metodia, joka on yksi Stream API:n keskeisistä pääteoperaatioista.
collect()-metodi
Se on yksi tehokkaimmista työkaluista virtojen käsittelyssä, mahdollistaen tulosten keräämisen esimerkiksi List-, Set- tai Map-kokoelmiin sekä monimutkaisten ryhmittelyjen ja tilastollisten laskentojen suorittamisen.
collect()-metodilla on kaksi toteutusta—tutustutaan molempiin.
collect()-metodin käyttö funktionaalisten rajapintojen kanssa
collect()-metodia Stream API:ssa voidaan käyttää kolmen funktionaalisen rajapinnan kanssa, mikä antaa täyden hallinnan datan keräämiseen:
Supplier<R> supplier– luo tyhjän kokoelman (R), johon elementit tallennetaan. EsimerkiksiArrayList::newalustaa uuden listan;BiConsumer<R, ? super T> accumulator– lisää virran elementit (T) kokoelmaan (R). EsimerkiksiList::addliittää alkiot listaan;BiConsumer<R, R> combiner– yhdistää kaksi kokoelmaa kun käytetään rinnakkaiskäsittelyä. EsimerkiksiList::addAllyhdistää listat yhdeksi.
Kaikki kolme komponenttia toimivat yhdessä tarjoten joustavuutta datan keräämiseen. Ensin supplier luo tyhjän kokoelman, jota käytetään virran elementtien keräämiseen. Tämän jälkeen accumulator lisää jokaisen elementin virran käsittelyn edetessä. Tämä prosessi pysyy suoraviivaisena sekventiaalisessa virrassa.
Kuitenkin, kun käytetään rinnakkaisvirtoja (parallelStream()), tilanne monimutkaistuu.
Tietojenkäsittely jaetaan useille säikeille, joista jokainen luo oman erillisen kokoelmansa. Kun käsittely on valmis, nämä yksittäiset kokoelmat täytyy yhdistää yhdeksi tulokseksi. Tässä vaiheessa combiner astuu kuvaan ja yhdistää tehokkaasti erilliset osat yhdeksi yhtenäiseksi kokoelmaksi.
Käytännön esimerkki
Työskentelet verkkokaupassa ja sinulla on tuotelista. Tehtävänäsi on kerätä vain ne tuotteet, joiden hinta on yli 500 $, käyttämällä collect()-metodia ja kolmea parametria.
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 + ")"; } }
Metodi collect() ottaa kolme argumenttia, joista jokainen määrittää eri vaiheen elementtien keräämisessä listaan:
-
ArrayList::new(Supplier) → luo tyhjänArrayList<Product>tulosten tallentamista varten; -
(list, product) -> list.add(product)(BiConsumer) → lisää jokaisenProduct-olion listaan, jos se täyttää suodatusehdon (price > 500); -
ArrayList::addAll(BiConsumer) → yhdistää useita listoja käytettäessä rinnakkaisvirtoja, varmistaen että kaikki suodatetut tuotteet yhdistetään yhdeksi listaksi.
Vaikka kolmas parametri on tarkoitettu pääasiassa rinnakkaiskäsittelyyn, se on collect()-metodissa pakollinen.
collect()-metodin käyttö Collector-rajapinnan kanssa
Lisäksi, että collect()-metodia voidaan käyttää kolmen funktionaalisen rajapinnan kanssa, sitä voidaan käyttää myös Collector-rajapinnan valmiiden toteutusten kanssa Stream API:ssa.
Tämä lähestymistapa on joustavampi ja käytännöllisempi, koska se tarjoaa sisäänrakennettuja metodeja kokoelmien käsittelyyn.
Collector<T, A, R> -rajapinta koostuu useista keskeisistä metodeista:
Supplier<A> supplier()– luo tyhjän säiliön elementtien keräämistä varten;BiConsumer<A, T> accumulator()– määrittää, miten elementit lisätään säiliöön;BinaryOperator<A> combiner()– yhdistää kaksi säiliötä kun käytetään rinnakkaiskäsittelyä;Function<A, R> finisher()– muuntaa säiliön lopulliseksi tulokseksi.
Kuten huomaat, tämä rakenne muistuttaa collect()-metodia, joka toimii funktionaalisten rajapintojen kanssa, mutta siihen on lisätty finisher()-metodi. Tämä lisävaihe mahdollistaa lisäkäsittelyn kerätyille tiedoille ennen kuin lopullinen tulos palautetaan—esimerkiksi listan lajittelu ennen palauttamista.
Lisäksi Collector-rajapinta tarjoaa characteristics()-metodin, joka määrittelee ominaisuudet, jotka auttavat optimoimaan streamin suorituksen:
Nämä ominaisuudet auttavat Stream APIa optimoimaan suorituskykyä. Esimerkiksi, jos kokoelma on luonteeltaan järjestämätön, UNORDERED-ominaisuuden määrittäminen voi estää tarpeettoman lajittelun, mikä tekee toiminnosta tehokkaamman.
Käytännön esimerkki
Kuvittele, että ylläpidät verkkokauppaa ja sinun täytyy käsitellä tuotteiden hintoja ennen niiden keräämistä. Esimerkiksi haluat pyöristää jokaisen hinnan lähimpään kokonaislukuun, poistaa duplikaatit ja lajitella lopullisen listan.
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); } }
Tietojen käsittely aloitetaan syöttämällä ne mukautettuun Collector-luokkaan nimeltä RoundedSortedCollector.
Tämä kerääjä kerää ensin kaikki hinnat Set<Integer>-kokoelmaan, jolloin päällekkäiset arvot poistuvat automaattisesti. Ennen arvon lisäämistä hinta pyöristetään käyttämällä Math.round(price) ja muunnetaan int-arvoksi. Esimerkiksi sekä 1200.99 että 1200.49 muuttuvat arvoksi 1200, kun taas 199.99 pyöristyy ylöspäin arvoksi 200.
Jos virta suoritetaan rinnakkaistilassa, combiner()-metodi yhdistää kaksi joukkoa lisäämällä kaikki toisen joukon alkiot toiseen. Tämä vaihe on olennainen monisäikeisissä ympäristöissä.
Viimeisessä vaiheessa, kun kaikki hinnat on kerätty, finisher()-metodi muuntaa joukon lajitelluksi listaksi. Se muuntaa Set<Integer>-joukon virraksi, käyttää sorted()-metodia arvojen järjestämiseksi nousevaan järjestykseen ja kerää ne sitten List<Integer>-listaan.
Tuloksena saadaan lajiteltu lista yksilöllisistä, pyöristetyistä hinnoista, joita voidaan käyttää jatko-laskentaan tai näyttötarkoituksiin.
1. Mitä collect()-metodi tekee Stream API:ssa?
2. Mitä lisäominaisuutta Collector-rajapinta tarjoaa verrattuna collect()-metodin käyttöön funktionaalisten rajapintojen kanssa?
Kiitos palautteestasi!