Что такое коллекторы java
Перейти к содержимому

Что такое коллекторы java

  • автор:

Стандартные методы свертки (Collectors) — Java: Стримы

Обработка коллекций через стримы в основном заканчивается двумя вариантами:

  • Из стрима формируется список через toList()
  • Стрим сворачивается в какое-то значение через reduce()

Второй случай распадается на различные варианты свертки, некоторые из которых встречаются очень часто. Чтобы не делать каждый раз одно и тоже, эти варианты сверток были реализованы в утилитарном классе Collectors в виде статических методов. В этом уроке мы рассмотрим эти методы.

Общий принцип работы этих методов такой. В конце цепочки вызывается метод collect() , куда передается конкретный коллектор. Пример подсчета количества элементов стрима:

import java.util.stream.Collectors; var numbers = List.of(1, 2, 3, 4, 5); var count = numbers.stream().collect(Collectors.counting()); System.out.println(count); // => 5 

Агрегирующие функции

Классическая свертка это разнообразные функции агрегации данных, такие как поиск суммы, минимального, максимального и среднего.

var numbers = List.of(1, 2, 3, 4, 5); var sum = numbers.stream() .collect(Collectors.summingInt(Integer::intValue)); System.out.println(sum); // => 15 

Метод summingInt() принимает на вход один параметр, с помощью которого извлекается значение из элемента коллекции. В нашем случае мы уже работаем с нужным значением, поэтому используется метод Integer.intValue() , который возвращает это же число. В случае использования объектов необходимость такой реализации очевиднее:

var total = employees.stream() .collect(Collectors.summingInt(Employee::getSalary)); // Если бы зарплата была типа Double // .collect(Collectors.summingDouble(Employee::getSalary)); 

Точно так же мы можем посчитать среднюю зарплату.

var average = employees.stream() .collect(Collectors.averagingInt(Employee::getSalary)); 

Для подсчета минимального и максимального понадобится добавить обертку в виде метода Comparator , который делает необходимое сравнение.

var min = employees.stream() .collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary))); var max = employees.stream() .collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary))); 

Конкатенация

Результат стрима можно преобразовать в строку с помощью Collectors.joining() .

var list = List.of("Apple", "Banana", "Cherry"); var result = list.stream() .collect(Collectors.joining(", ")); System.out.println(result); // => Apple, Banana, Cherry 

При необходимости можно указать символы для оборачивания получившейся строки.

var list = List.of("Apple", "Banana", "Cherry"); var result = list.stream() .collect(Collectors.joining(", ", "[", "]")); System.out.println(result); // => [Apple, Banana, Cherry] 

Группировка

Самый интересный вариант использования Collectors это группировка значений коллекции по каким-то признакам. Если предположить что у нас есть список сотрудников, то мы можем сгруппировать его по подразделению в котором они работают. На выходе получится Map .

ListEmployee> employees = Arrays.asList( new Employee("John Doe", "IT", 70000), new Employee("Jane Smith", "HR", 75000), new Employee("Mary Johnson", "IT", 60000), new Employee("Mike Wilson", "Marketing", 65000) ); // Map> var employeesByDepartment = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); System.out.println(employeesByDepartment); // => // HR=[Employee(name=Jane Smith, department=HR, salary=75000)], // IT=[Employee(name=John Doe, department=IT, salary=70000), Employee(name=Mary Johnson, department=IT, salary=60000)], // Marketing=[Employee(name=Mike Wilson, department=Marketing, salary=65000)] // > 

Существует и более сложная версия группировки, в которой к получившейся группе применяется дополнительная свертка. Например так можно посчитать количество слов в тексте. Ниже пример этого кода, в котором для простоты текст уже разбит на список слов.

var words = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple"); // Map var wordCounts = words.stream() .collect(Collectors.groupingBy(word -> word, Collectors.counting())); System.out.println(wordCounts); // => 

Как работает этот код:

  • Так как в нашем списке обычные слова, а не объекты, лямбда имеет такой вид word -> word .
  • Вторым параметром в Collectors.groupingBy() передается другой метод коллектора, который выполняется независимо для каждой получившейся группы. Результат этой свертки становится значением в результирующем Map . В примере выше, вместо списка слов мы получаем числовое значение.

Партицирование

Партицирование, это вариант свертки, в котором список значений делится на две группы по заданному условию. Выполняется с помощью метода Collectors.partitioningBy() .

// Map> var groups = numbers.stream() .collect(Collectors.partitioningBy(n -> n % 2 == 0)); // 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Коллекторы Stream API простыми словами

Всем доброго времени суток. Сегодня обратимся к очень интересному понятию коллекторов, разберем самые часто встречающиеся примеры и посмотрим на то, как это можно примерить на практике. Надеюсь, что кто-то сможет освежить знания, а кто-то заинтересуется и начнет изучать тему.

Такое понятие как Stream API вошло в понимание Java-разработчиков с приходом 8 версии JDK. И в плане использования «стримы» оказали довольно сильное влияние на будущий код. Многие по достоинству оценили лаконичность получаемого кода, когда можно не писать громоздкие блоки for или while.

Однако часто возникают пробелы в понимании того, как работают стримы. Ведь при их применении могут возникать различного рода overhead’ы, а то и вовсе проблемы с реализацией необходимой логики. Известно, что стримы содержат операции, но хотелось бы остановиться на одной из них – терминальной операции Collector. Это часто употребляемый шаг в стримах, однако мало кто задумывается о нюансах его работы.

Рассмотрим особенности работы коллекторов и разберем несколько наиболее часто встречающихся примеров.

Начнем с того, что коллекторы – это объекты, разработанные для какой-либо агрегации потоковых данных. Поток, пройдя через цепочку отображений, фильтров и других не терминальных операций, по итогу представляет из себя поток объектов. Их необходимо либо собрать воедино, либо выбрать какой-то один, а может быть, и вообще получить новый информационный объект на основе каких-то характеристик. В этой точке и могут быть применены коллекторы.

На чем основана агрегация в коллекторах? В их основу заложены 4 основополагающих принципа:

Нужен для создания новых результирующих контейнеров. Ведь «прилетающие» в потоке данные необходимо куда-то складывать;

Занимается сложением, или аккумуляцией данных. Аккумулятор складирует приходящие данные во временные контейнеры;

Combiner объединяет полученные временные контейнеры в более крупный, что по итогу приводит к получению агрегированных данных;

Опционально, но не обязательно, может присутствовать Finisher, который проведет post-обработку полученного глобального контейнера и вернет уже результат в качестве итога работы коллектора.

Таким образом, реализовав необходимые интерфейсы описанных четырех принципов, можно создать сколь угодно сложный коллектор, который будет выполнять необходимую агрегацию данных, если вы не смогли найти готовое решение из уже реализованных в Java Core.

Реализация кастомного коллектора воплощена в методе of:

Рассмотрим часть коллекторов, уже существующих в Java Core, на базе утилитного класса Collectors.

Наиболее часто употребляемые коллекторы агрегации данных в коллекции:

Соберет данные в List<> на базе ArrayList<>:

Соберет данные в Set<> на базе HashSet<>:

Соберет данные в Map<> на базе HashMap<>:

Соберет данные в коллекцию, указанную разработчиком:

Помимо чистой агрегации данных в массивы и коллекции, существует серия коллекторов для группировки данных по какому-либо признаку. Например, groupingBy по переданному классификатору classifier создаст Map<>, в котором в качестве key будет результат классификатора, а value – агрегация от вторичного коллектора downstream. Вторичным коллектором может выступать абсолютно любой коллектор, главное понимать, что на вход этот коллектор пример тот же поток данных, что и классификатор.

Следующий на очереди – коллектор отображения. Он произведет отображение входящего потока в поток отображения и соберет всё вторичным коллектором downstream. Часто заменяется обычным отображением через промежуточную операцию map:

Также существует целая серия коллекторов по математическим операциям суммирования, усреднения, поиска минимального и максимального числа, какой-либо редукции, объединения строк и т.д. Стоит отметить отдельные коллекторы для многопоточных реализаций. Например:

Он соберет данные в ConcurrentMap<> на базе такого же HashMap<>, где в качестве key будет результат от работы классификатора classifier, а в качестве value – агрегация от уже описанного выше коллектора toList().

Напоследок еще один любопытный коллектор:

Он произведет ту самую post-обработку результата коллектора downstream функционалом finisher’а, о котором было упомянуто ранее, а результатом будет его итог.

Рассмотрим практический пример конструирования собственного коллектора через метод of, указанный в начале статьи. Соберем проект на Gradle. Для удобства в зависимость добавим библиотеку Guava:

Был смоделирован тестовый поток целочисленных данных от 0 до 99, данные были собраны в LinkedList, и в finisher’е проведена post-обработка по подсчету четных цифр. Так можно создать абсолютно любой коллектор без ограничения по логике.

На этом завершим краткий обзор коллекторов простыми словами.

Успешной разработки на Java Stream API.

Буду рад ответить на вопросы, если будут 🙂

Что такое коллекторы java

На этом шаге мы рассмотрим интерфейс Collector и класс Collectors

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *