Collect() Stream-Elementtien Kerääminen Kokoelmaan
Pyyhkäise näyttääksesi valikon
Olet jo tutustunut pääteoperaatioihin ja käyttänyt niitä aiemmissa esimerkeissä ja harjoituksissa. Nyt on aika tarkastella tarkemmin, miten ne toimivat. 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 List-, Set- tai Map-rakenteisiin sekä monimutkaisten ryhmittelyjen ja tilastollisten laskentojen suorittamisen.
collect()-metodista on olemassa kaksi toteutusta—tutustutaan molempiin.
collect()-metodin käyttö funktionaalisten rajapintojen kanssa
Stream API:n collect()-metodia voidaan käyttää kolmen funktionaalisen rajapinnan kanssa, mikä antaa täyden hallinnan datan keräämiseen:
Supplier<R> supplier– luo tyhjän kokoelman (R), johon alkioita tallennetaan. EsimerkiksiArrayList::newalustaa uuden listan;BiConsumer<R, ? super T> accumulator– lisää virran alkiot (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 alkioiden keräämiseen. Tämän jälkeen accumulator lisää jokaisen alkion virran käsittelyn aikana. 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 tuotteet, joiden hinta on yli 500 $, käyttämällä collect()-metodia kolmella parametrilla.
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 + ")"; } }
collect()-metodi ottaa kolme argumenttia, joista jokainen määrittelee 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ää suodatuskriteerin (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 silti pakollinen collect()-metodissa.
collect()-metodin käyttö Collector-rajapinnan kanssa
Lisäksi, että collect()-metodia voidaan käyttää kolmen funktionaalisen rajapinnan kanssa, sitä voidaan hyödyntää myös Stream API:ssa valmiiden Collector-rajapinnan toteutusten kanssa.
Tämä lähestymistapa on joustavampi ja käytännöllisempi, sillä se tarjoaa sisäänrakennetut metodit kokoelmien käsittelyyn.
Collector<T, A, R>-rajapinta sisältää useita keskeisiä metodeja:
Supplier<A> supplier()– luo tyhjän säiliön elementtien keräämistä varten;BiConsumer<A, T> accumulator()– määrittelee, 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-määrittely 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 kokonaisluvuksi (int). 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 joukkoon. 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