Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте Collect() Збір Елементів Потоку У Колекцію | Термінальні Операції у Stream API
Stream API

bookCollect() Збір Елементів Потоку У Колекцію

Ви вже знайомі з термінальними операціями та навіть використовували їх у попередніх прикладах і вправах. Тепер настав час детальніше розглянути, як вони працюють. Першою розглянемо метод collect(), який є однією з ключових термінальних операцій у Stream API.

Метод collect()

Це один із найпотужніших інструментів при роботі зі стрімами, що дозволяє накопичувати результати у List, Set або Map, а також виконувати складні групування та статистичні обчислення.

Існують дві реалізації методу collect() — давайте розглянемо обидві.

Використання collect() з функціональними інтерфейсами

Метод collect() у Stream API може використовуватися з трьома функціональними інтерфейсами для повного контролю над збиранням даних:

  • Supplier<R> supplier – створює порожню колекцію (R), у яку будуть зберігатися елементи. Наприклад, ArrayList::new ініціалізує новий список;
  • BiConsumer<R, ? super T> accumulator – додає елементи потоку (T) до колекції (R). Наприклад, List::add додає елементи до списку;
  • BiConsumer<R, R> combiner – об'єднує дві колекції при паралельній обробці. Наприклад, List::addAll об'єднує списки в один.

Усі три компоненти працюють разом, забезпечуючи гнучкість у збиранні даних. Спочатку supplier створює порожню колекцію, яка буде використовуватися для накопичення елементів із потоку. Далі accumulator додає кожен елемент у процесі обробки потоку. Такий підхід залишається простим у послідовному потоці.

Однак при роботі з паралельними потоками (parallelStream()) процес ускладнюється.

Обробка даних виконується у декількох потоках, при цьому кожен потік створює власну окрему колекцію. Після завершення обробки ці окремі колекції необхідно об'єднати в єдиний результат. Саме тут використовується combiner, який ефективно поєднує окремі частини в одну уніфіковану колекцію.

Практичний приклад

Ви працюєте в інтернет-магазині та маєте список товарів. Ваше завдання — зібрати лише ті товари, які коштують понад $500, використовуючи метод collect() з трьома параметрами.

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

Метод collect() приймає три аргументи, кожен з яких визначає окремий етап збору елементів у список:

  • ArrayList::new (Supplier) → створює порожній ArrayList<Product> для зберігання результатів;

  • (list, product) -> list.add(product) (BiConsumer) → додає кожен Product до списку, якщо він відповідає умові фільтрації (price > 500);

  • ArrayList::addAll (BiConsumer) → об'єднує кілька списків при використанні паралельних потоків, забезпечуючи об'єднання всіх відфільтрованих продуктів у єдиний список.

Хоча третій параметр в основному використовується для паралельної обробки, він є обов'язковим для collect().

Використання collect() з інтерфейсом Collector

Окрім роботи з трьома функціональними інтерфейсами, метод collect() у Stream API також може використовуватися з готовими реалізаціями інтерфейсу Collector.

Цей підхід є більш гнучким та зручним, оскільки надає вбудовані методи для роботи з колекціями.

Інтерфейс Collector<T, A, R> складається з кількох основних методів:

  • Supplier<A> supplier() – створює порожній контейнер для накопичення елементів;
  • BiConsumer<A, T> accumulator() – визначає, як елементи додаються до контейнера;
  • BinaryOperator<A> combiner() – об'єднує два контейнери при використанні паралельної обробки;
  • Function<A, R> finisher() – перетворює контейнер у кінцевий результат.

Як видно, ця структура подібна до методу collect(), який працює з функціональними інтерфейсами, але вводить метод finisher(). Цей додатковий крок дозволяє виконати додаткову обробку зібраних даних перед поверненням остаточного результату—наприклад, відсортувати список перед поверненням.

Крім того, інтерфейс Collector надає метод characteristics(), який визначає властивості, що допомагають оптимізувати виконання стріму:

Ці властивості допомагають Stream API оптимізувати продуктивність. Наприклад, якщо колекція за своєю природою неупорядкована, вказівка UNORDERED може запобігти зайвому сортуванню, зробивши операцію більш ефективною.

Практичний приклад

Уявіть, що ви керуєте інтернет-магазином і вам потрібно обробити ціни товарів перед їх збором. Наприклад, потрібно округлити кожну ціну до найближчого цілого числа, видалити дублікати та відсортувати фінальний список.

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

Обробка даних починається з передачі їх у спеціальний Collector під назвою RoundedSortedCollector.

Цей колектор спочатку накопичує всі ціни у Set<Integer>, що автоматично забезпечує видалення дублікатів. Перед додаванням кожного значення ціна округлюється за допомогою Math.round(price) і перетворюється на int. Наприклад, як 1200.99, так і 1200.49 стануть 1200, а 199.99 буде округлено до 200.

Якщо потік виконується у паралельному режимі, метод combiner() об'єднує дві множини, додаючи всі елементи з однієї множини до іншої. Цей етап є важливим для багатопоточного середовища.

На фінальному етапі, після збору всіх цін, метод finisher() перетворює множину на відсортований список. Він перетворює Set<Integer> у потік, застосовує sorted() для впорядкування значень у зростаючому порядку і збирає їх у List<Integer>.

У результаті ви отримуєте відсортований список унікальних, округлених цін, який можна використовувати для подальших обчислень або відображення.

1. Що робить метод collect() у Stream API?

2. Яку додаткову можливість надає інтерфейс Collector порівняно з collect() із функціональними інтерфейсами?

question mark

Що робить метод collect() у Stream API?

Select the correct answer

question mark

Яку додаткову можливість надає інтерфейс Collector порівняно з collect() із функціональними інтерфейсами?

Select the correct answer

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 1

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Awesome!

Completion rate improved to 2.33

bookCollect() Збір Елементів Потоку У Колекцію

Свайпніть щоб показати меню

Ви вже знайомі з термінальними операціями та навіть використовували їх у попередніх прикладах і вправах. Тепер настав час детальніше розглянути, як вони працюють. Першою розглянемо метод collect(), який є однією з ключових термінальних операцій у Stream API.

Метод collect()

Це один із найпотужніших інструментів при роботі зі стрімами, що дозволяє накопичувати результати у List, Set або Map, а також виконувати складні групування та статистичні обчислення.

Існують дві реалізації методу collect() — давайте розглянемо обидві.

Використання collect() з функціональними інтерфейсами

Метод collect() у Stream API може використовуватися з трьома функціональними інтерфейсами для повного контролю над збиранням даних:

  • Supplier<R> supplier – створює порожню колекцію (R), у яку будуть зберігатися елементи. Наприклад, ArrayList::new ініціалізує новий список;
  • BiConsumer<R, ? super T> accumulator – додає елементи потоку (T) до колекції (R). Наприклад, List::add додає елементи до списку;
  • BiConsumer<R, R> combiner – об'єднує дві колекції при паралельній обробці. Наприклад, List::addAll об'єднує списки в один.

Усі три компоненти працюють разом, забезпечуючи гнучкість у збиранні даних. Спочатку supplier створює порожню колекцію, яка буде використовуватися для накопичення елементів із потоку. Далі accumulator додає кожен елемент у процесі обробки потоку. Такий підхід залишається простим у послідовному потоці.

Однак при роботі з паралельними потоками (parallelStream()) процес ускладнюється.

Обробка даних виконується у декількох потоках, при цьому кожен потік створює власну окрему колекцію. Після завершення обробки ці окремі колекції необхідно об'єднати в єдиний результат. Саме тут використовується combiner, який ефективно поєднує окремі частини в одну уніфіковану колекцію.

Практичний приклад

Ви працюєте в інтернет-магазині та маєте список товарів. Ваше завдання — зібрати лише ті товари, які коштують понад $500, використовуючи метод collect() з трьома параметрами.

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

Метод collect() приймає три аргументи, кожен з яких визначає окремий етап збору елементів у список:

  • ArrayList::new (Supplier) → створює порожній ArrayList<Product> для зберігання результатів;

  • (list, product) -> list.add(product) (BiConsumer) → додає кожен Product до списку, якщо він відповідає умові фільтрації (price > 500);

  • ArrayList::addAll (BiConsumer) → об'єднує кілька списків при використанні паралельних потоків, забезпечуючи об'єднання всіх відфільтрованих продуктів у єдиний список.

Хоча третій параметр в основному використовується для паралельної обробки, він є обов'язковим для collect().

Використання collect() з інтерфейсом Collector

Окрім роботи з трьома функціональними інтерфейсами, метод collect() у Stream API також може використовуватися з готовими реалізаціями інтерфейсу Collector.

Цей підхід є більш гнучким та зручним, оскільки надає вбудовані методи для роботи з колекціями.

Інтерфейс Collector<T, A, R> складається з кількох основних методів:

  • Supplier<A> supplier() – створює порожній контейнер для накопичення елементів;
  • BiConsumer<A, T> accumulator() – визначає, як елементи додаються до контейнера;
  • BinaryOperator<A> combiner() – об'єднує два контейнери при використанні паралельної обробки;
  • Function<A, R> finisher() – перетворює контейнер у кінцевий результат.

Як видно, ця структура подібна до методу collect(), який працює з функціональними інтерфейсами, але вводить метод finisher(). Цей додатковий крок дозволяє виконати додаткову обробку зібраних даних перед поверненням остаточного результату—наприклад, відсортувати список перед поверненням.

Крім того, інтерфейс Collector надає метод characteristics(), який визначає властивості, що допомагають оптимізувати виконання стріму:

Ці властивості допомагають Stream API оптимізувати продуктивність. Наприклад, якщо колекція за своєю природою неупорядкована, вказівка UNORDERED може запобігти зайвому сортуванню, зробивши операцію більш ефективною.

Практичний приклад

Уявіть, що ви керуєте інтернет-магазином і вам потрібно обробити ціни товарів перед їх збором. Наприклад, потрібно округлити кожну ціну до найближчого цілого числа, видалити дублікати та відсортувати фінальний список.

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

Обробка даних починається з передачі їх у спеціальний Collector під назвою RoundedSortedCollector.

Цей колектор спочатку накопичує всі ціни у Set<Integer>, що автоматично забезпечує видалення дублікатів. Перед додаванням кожного значення ціна округлюється за допомогою Math.round(price) і перетворюється на int. Наприклад, як 1200.99, так і 1200.49 стануть 1200, а 199.99 буде округлено до 200.

Якщо потік виконується у паралельному режимі, метод combiner() об'єднує дві множини, додаючи всі елементи з однієї множини до іншої. Цей етап є важливим для багатопоточного середовища.

На фінальному етапі, після збору всіх цін, метод finisher() перетворює множину на відсортований список. Він перетворює Set<Integer> у потік, застосовує sorted() для впорядкування значень у зростаючому порядку і збирає їх у List<Integer>.

У результаті ви отримуєте відсортований список унікальних, округлених цін, який можна використовувати для подальших обчислень або відображення.

1. Що робить метод collect() у Stream API?

2. Яку додаткову можливість надає інтерфейс Collector порівняно з collect() із функціональними інтерфейсами?

question mark

Що робить метод collect() у Stream API?

Select the correct answer

question mark

Яку додаткову можливість надає інтерфейс Collector порівняно з collect() із функціональними інтерфейсами?

Select the correct answer

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 1
some-alt