Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Oppiskele Collect(): Virran Alkioiden Kerääminen Kokoelmaan | Stream API:n Pääteoperaatiot
Stream API

bookCollect(): 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. Esimerkiksi ArrayList::new alustaa uuden listan;
  • BiConsumer<R, ? super T> accumulator – lisää virran elementit (T) kokoelmaan (R). Esimerkiksi List::add liittää alkiot listaan;
  • BiConsumer<R, R> combiner – yhdistää kaksi kokoelmaa kun käytetään rinnakkaiskäsittelyä. Esimerkiksi List::addAll yhdistää 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

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

Metodi collect() ottaa kolme argumenttia, joista jokainen määrittää eri vaiheen elementtien keräämisessä listaan:

  • ArrayList::new (Supplier) → luo tyhjän ArrayList<Product> tulosten tallentamista varten;

  • (list, product) -> list.add(product) (BiConsumer) → lisää jokaisen Product-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

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

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?

question mark

Mitä collect()-metodi tekee Stream API:ssa?

Select the correct answer

question mark

Mitä lisäominaisuutta Collector-rajapinta tarjoaa verrattuna collect()-metodin käyttöön funktionaalisten rajapintojen kanssa?

Select the correct answer

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 3. Luku 1

Kysy tekoälyä

expand

Kysy tekoälyä

ChatGPT

Kysy mitä tahansa tai kokeile jotakin ehdotetuista kysymyksistä aloittaaksesi keskustelumme

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(): 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. Esimerkiksi ArrayList::new alustaa uuden listan;
  • BiConsumer<R, ? super T> accumulator – lisää virran elementit (T) kokoelmaan (R). Esimerkiksi List::add liittää alkiot listaan;
  • BiConsumer<R, R> combiner – yhdistää kaksi kokoelmaa kun käytetään rinnakkaiskäsittelyä. Esimerkiksi List::addAll yhdistää 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

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

Metodi collect() ottaa kolme argumenttia, joista jokainen määrittää eri vaiheen elementtien keräämisessä listaan:

  • ArrayList::new (Supplier) → luo tyhjän ArrayList<Product> tulosten tallentamista varten;

  • (list, product) -> list.add(product) (BiConsumer) → lisää jokaisen Product-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

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

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?

question mark

Mitä collect()-metodi tekee Stream API:ssa?

Select the correct answer

question mark

Mitä lisäominaisuutta Collector-rajapinta tarjoaa verrattuna collect()-metodin käyttöön funktionaalisten rajapintojen kanssa?

Select the correct answer

Oliko kaikki selvää?

Miten voimme parantaa sitä?

Kiitos palautteestasi!

Osio 3. Luku 1
some-alt