Одна из наиболее интересных функций, представленных в Java Streams, - это возможность легко группировать коллекции с помощью сборщика groupingBy.
Хотя в некоторых случаях это может быть довольно просто, существуют различные комбинации groupingBy, и некоторые из них не так очевидны для понимания многими разработчиками, поэтому я решил, что было бы полезно изучить их вместе.

Прежде всего, давайте начнем с рассмотрения того, что предлагает нам Java API в классе Collectors.

Вступление

Java предлагает комбинацию сборщиков для группирования элементов Java Stream, давайте перечислим их все, прежде чем мы продолжим:

Как видите, существует три разных типа сборщиков, и помимо них у нас есть «параллельная» версия для каждого из них.

Нет ничего лучше, чем пример для ясного объяснения каждого случая, давайте начнем!

Группировать элементы на карте по ключу

Первый способ - взять каждый элемент в нашей коллекции и назначить его ключу с помощью классификатора; наш сборщик поместит каждый из этих элементов в Java Map, связывая List с каждым существующим ключом в Map.

Я знаю, что это может показаться запутанным, поэтому давайте посмотрим на это на примере. Мы собираемся сгруппировать наш список сотрудников по полу. Посмотрим, как это будет выглядеть:

Это самый простой из случаев, мы просто указываем классификатор, который представляет собой функцию, которая принимает каждый элемент в коллекции и возвращает ключ; этот ключ будет значением, используемым для присвоения каждого элемента данной группе.

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

В нашем примере есть только две группы, потому что поле пол имеет только два значения: МУЖСКОЙ и ЖЕНСКИЙ. В результате мы получаем Map ‹Sex, List ‹Employee››.
Это структура по умолчанию, которую мы получим, когда будем использовать сборщик groupingBy в Java ( Карта ‹Ключ, Список ‹Element››).

Давайте теперь посмотрим, что произойдет, если нам потребуется изменить эту структуру по умолчанию.

Группировка по ключу с указанием другого сборщика

Предположим, что вместо того, чтобы сохранять наши результаты в List, как это делает по умолчанию наш сборщик groupingBy, мы хотим сохранить наши элементы в Set; например, мы можем захотеть избежать дублирования.

В этом случае предоставляется другой метод, который принимает «нисходящий» сборщик, который мы можем использовать для преобразования нашей результирующей коллекции для каждой группы по своему усмотрению. В нашем примере это будет очень просто:

Как видите, коллекции теперь хранятся в Сете. Что, если мы хотим собирать эти данные по-другому? Мы могли бы захотеть использовать любую операцию агрегирования; например, нам может потребоваться вычислить средний возраст для каждой группы. Для этого мы можем использовать сборщик averagingInt в классе Collectors.

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

Что еще позволяет нам делать Java? Мы также могли бы сгруппировать по второстепенному полю! Представьте, что мы хотим сгруппировать сначала по полу, а затем по возрасту, так просто:

Есть несколько комбинаций, которые мы можем использовать с этими сборщиками, API, который Java предоставляет нам для простой обработки коллекций, очень мощный.

Посмотрим еще на один пример. что, если мы хотим найти самого молодого сотрудника в каждой возрастной группе? И очень просто, так просто:

Мы используем коллектор minBy, который принимает Компаратор; Java теперь предоставляет очень простой способ предоставить компараторы с помощью Comparator.comparing (keyExtractor). KeyExtractor - это функция, поэтому в этом случае достаточно передать ссылку на метод.

Это здорово, правда? Управление данными с помощью Java Streams с использованием функционального стиля значительно упростило жизнь разработчикам!

Мы даже можем фильтровать некоторых сотрудников по условию после того, как они сгруппированы; например, давайте сгруппируем всех сотрудников старше тридцати по полу:

Очень просто за счет использования фильтрующего сборщика в классе Collectors; фильтрация была введена в JDK 9, поэтому убедитесь, что вы используете более раннюю версию, чем JDK 8, чтобы иметь возможность ее использовать!

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

Давайте теперь посмотрим на третий вариант группировки!

Группировка элементов с указанием того, какую карту использовать

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

Давайте посмотрим на примере, как мы можем его использовать. в нашем примере, хотя это будет выглядеть довольно глупо и бесполезно, мы собираемся инициализировать карту существующей картой с парой элементов:

Как видите, мы говорим нашему сборщику начать с существующей карты, которая предоставляется поставщиком, которого мы указываем в аргументе «mapFactory» нашего groupingBy.

Что интересно в нашем примере, так это то, что после запуска кода вы увидите, что наша последняя карта содержит сотрудников младше тридцати! Это почему? Это связано с тем, что наши исходные элементы, представленные в исходной карте, не обрабатываются сборщиком фильтрации. Это важно учитывать, если вам когда-нибудь понадобится использовать аргумент mapFactory.

Группировка элементов одновременно

После ознакомления с тремя основными методами, предоставляемыми коллекторами groupingBy, мы знаем, что для каждого из них существует параллельная версия.

Для выполнения этих методов нам понадобится параллельный поток, который вернет ConcurrentHashMap. В этом методе установлены флаги CONCURRENT и UNORDERED, что означает, что несколько потоков будут накапливать элементы в одном аккумуляторе вместо объединения нескольких аккумуляторов; в этом случае наш единственный аккумулятор - это ConcurrentHashMap.

Итак, все о groupingBy! Надеюсь, вам понравилось!

Если вам все еще сложно понять потоки Java и функциональное программирование в целом, я бы порекомендовал вам прочитать Функциональное программирование на Java: использование возможностей лямбда-выражений Java 8. ; Вы можете купить его на Amazon по следующей ссылке.

Заключение

До того, как был выпущен JDK 8, группировка элементов никогда не была такой простой, как сейчас; Если учесть объем кода и недостаточную читаемость, которых мы могли бы достичь, написав любой из примеров, которые мы видели в этой статье, с использованием простого кода Java 7, разница будет существенной.

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

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

Так что это от меня! Я очень надеюсь, что вы узнали что-то сегодня и сможете использовать наши знания в своей повседневной работе в качестве разработчика. Я с нетерпением жду встречи с вами в ближайшее время и, пожалуйста, подпишитесь на меня, если вам нравятся мои статьи, и вы хотите получить уведомление, когда моя следующая статья будет опубликована!







Большое спасибо за чтение!