1. Збирання елементів

І нарешті ми дійшли до найцікавішого методу в класі Stream — методу collect(). Він використовується для того, щоб перейти від потоків до звичних колекцій — List<T>, Set<T>, Map<T, R> та інших.

У метод collect() потрібно передати спеціальний об'єкт — collector. Цей об'єкт зчитує всі дані з потоку, перетворює їх на певну колекцію та повертає її. А слідом за ним цю саму колекцію повертає й сам метод collect.

Усе це влаштовано досить хитро: об'єкт collector має тип Collector<T, A, R> — у нього аж три типи-параметри. Останній тип R зазвичай і є типом на кшталт List<T>. Тому компілятор може за цим типом підставити правильний тип результату самого методу collect().

Сподіваємося, ви не сильно заплуталися. У будь-якому разі власноруч створювати об'єкти типу Collector вам не потрібно. Досить просто скористатися вже готовими об'єктами, що їх повертають статичні методи класу Collectors.

Клас Collectors

Клас Collectors має кілька статичних методів, які повертають готові об'єкти-колектори на всі випадки життя. Їх кілька десятків, але ми розглянемо основні:

toList()
Об'єкт, який перетворює потік на список List<T>
toSet()
Об'єкт, який перетворює потік на множину Set<T>
toMap()
Об'єкт, який перетворює потік на мапу Map<K, V>
joining()
Склеює елементи потоку в один рядок
mapping()
Перетворює елементи потоку на Map<K, V>
groupingBy()
Групує елементи, повертає Map <K, V>

2. Перетворення потоку на список

Отакий вигляд має типова робота з потоком і перетворення результату роботи на список:

ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Привіт", "як", "справи?");

List<String> result = list.stream()
   .filter( s -> Character.isUpperCase(s.charAt(0)) )
   .collect( Collectors.toList() );

Ми отримали потік із колекції, потім із нього отримали новий потік, відфільтрувавши тільки рядки, перший символ яких записано у верхньому регістрі. Відтак усі дані з останнього потоку зібрали в колекцію та повернули її.



3. Перетворення потоку на множину

Отакий вигляд має типова робота з потоком і перетворення результату роботи на множину:

ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "Привіт", "як", "справи?");

Set<String> result = list.stream()
   .filter( s -> Character.isUpperCase(s.charAt(0)) )
   .collect( Collectors.toSet() );

Усе дуже схоже на код перетворення потоку на List, тільки використовується інший об'єкт-колектор, що його повертає метод toSet();



4. Перетворення потоку на мапу

А от перетворити потік на мапу трохи складніше, адже кожен об'єкт Map складається з двох елементів — ключа і значення. Нам треба придумати, як в елемента потоку ми визначатимемо ключ, а як — значення.

Приклад.

ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "a=2", "b=3", "c=4", "d==3");

Map<String, String> result = list.stream()
   .map( e -> e.split("=") )
   .filter( e -> e.length == 2 )
   .collect( Collectors.toMap(e -> e[0], e -> e[1]) );

Спробуймо розібратися, що тут відбувається.

У першому рядку map(…) ми перетворюємо кожен рядок на масив рядків. Використовуючи метод split, ми ділимо кожен рядок на дві частини. Роздільником є знак рівності.

У другому рядку — метод filter() — ми пропускаємо крізь фільтр лише ті елементи-масиви, які містять рівно два елементи. Елемент d == 3 було розбито на масив із трьох елементів — він фільтр не пройде.

І насамкінець в останньому рядку ми перетворюємо потік на Map<String, String>. У метод toMap() передаються дві функції. Для кожного елемента потоку перша функція має повернути ключ, а друга — значення.

У нас ключем буде перший елемент масиву ("a", "b", "c"), а значенням — другий елемент масиву: "2", "3", "4".



5. Перетворення потоку на рядок

Ще один цікавий об'єкт-колектор — це Collectors.joining(). Він перетворює всі елементи потоку на тип String і склеює їх в один рядок. Приклад:

ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "a=2", "b=3", "c=4", "d==3");
String result = list.stream().collect( Collectors.joining(", ") );