Научитесь использовать метод Collectors.groupingBy() для группировки и агрегации элементов потока аналогично предложению « GROUP BY » в SQL.
Поток -> groupingBy() -> Карта элементов после применения операции «группировать по»
1. Метод Collectors.groupingBy()
1.1 Синтаксис
Метод groupingBy() возвращает Collector, реализующий операцию «GROUP BY» над элементами потока, и возвращает результат в виде карты.
группировкаПо(классификатор)группировкаПо(классификатор, коллектор)группировкаПо(классификатор, поставщик, сборщик)
Мы можем передать этому методу следующие аргументы:
- классификатор: сопоставляет входные элементы с ключами карты
- collector: — это функция сокращения потока. По умолчанию используется Collectors.toList(), которая приводит к тому, что сгруппированные элементы превращаются в Список.
- поставщик: предоставляет новую пустую карту, в которую будут вставлены результаты. По умолчанию используется HashMap::new. Мы можем использовать другие карты, такие как TreeMap, LinkedHashMap или ConcurrentMap , чтобы вставить дополнительное поведение в процесс группировки, например сортировку.
1.2 Использование groupingByConcurrent() для параллельной обработки
Мы можем использовать Collectors.groupingByConcurrent(), если мы хотим обрабатывать элементы потока параллельно, что использует многоядерную архитектуру машины и возвращает ConcurrentMap. За исключением параллелизма, он работает аналогично методу groupingBy().
группировкаПоКонкуренту(классификатор)группировкаПоКонкуренту(классификатор, сборщик)группировкаПоКонкуренту(классификатор, поставщик, сборщик)
2. Группировка коллекционеровПо примерам
Для демонстрационных целей мы создадим две записи Person и Department следующим образом.
record Person(int id, String name, double salary, Department department) {}record Department(int id, String name) {}
И мы создаем список лиц, который будем использовать для создания потока и сбора записей в различных группах.
List<Person> persons = List.of(new Person(1, "Alex", 100d, new Department(1, "HR")),new Person(2, "Brian", 200d, new Department(1, "HR")),new Person(3, "Charles", 900d, new Department(2, "Finance")),new Person(4, "David", 200d, new Department(2, "Finance")),new Person(5, "Edward", 200d, new Department(2, "Finance")),new Person(6, "Frank", 800d, new Department(3, "ADMIN")),new Person(7, "George", 900d, new Department(3, "ADMIN")));
2.1 Группировка по простому условию
Давайте начнем с простого условия, чтобы лучше понять использование. В следующем примере мы группируем всех лиц по отделу. Это создаст Map<Department, List<Person>>, где ключ карты — записи отделов, а значение карты — все лица в этом отделе.
Карта<Отдел, Список<Персона>> map = persons.stream().collect(groupingBy(Person::department));System.out.println(карта);
Вывод программы.
{Department[id=2, name=Finance]=[Person[id=3, ...],Person[id=4, ...],Person[id=5, ...],Department[id=3, name=ADMIN]=[Person[id=6, ...],Person[id=7, ...],Department[id=1, name=HR]=[Person[id=1, ...],Person[id=2, ...]}
Аналогично, если мы хотим собрать только идентификаторы людей во всех отделах, то мы можем использовать Collectors.mapping() для сбора всех идентификаторов людей в списке вместо сбора записей о людях.
Карта<Отдел, Список<Целое число>> карта = persons.stream().collect(groupingBy(Person::department, mapping(Person::id, toList())));System.out.println(карта);
Вывод программы.
{Department[id=2, name=Finance]=[3, 4, 5],Department[id=3, name=ADMIN]=[6, 7],Department[id=1, name=HR]=[1, 2]}
2.2 Группировка по сложному условию
Могут быть случаи, когда нам нужно применить сложное условие для группировки. В этом случае Map может представить условие с помощью кортежа Java, а затем сгруппировать соответствующие элементы как список в значении Map.
В следующем примере мы хотим сгруппировать по разным отделам и парам зарплат. В значении Map мы получим список лиц, которые имеют один и тот же отдел и одинаковую зарплату.
Карта<Объект, Список<Целое число>> карта = persons.stream().collect(groupingBy(person -> new Pair<>(person.salary(), person.department()),отображение(Person::id, toList())));System.out.println(карта);
Вывод программы ясно показывает, что лица 4 и 5 работают в одном отделе и имеют одинаковую зарплату.
{[900.0, Department[id=3, name=ADMIN]]=[7],[800.0, Department[id=3, name=ADMIN]]=[6],[200.0, Department[id=2, name=Finance]]=[4, 5],[900.0, Department[id=2, name=Finance]]=[3],[200.0, Department[id=1, name=HR]]=[2],[100.0, Department[id=1, name=HR]]=[1]}
2.3 Группировка с подсчетом
Мы также можем агрегировать значения, выполняя другие операции, такие как подсчет(), усреднение(), суммирование() и т. д. Это помогает получить операцию сокращения значений Map для получения одного значения.
В следующем примере мы подсчитываем всех сотрудников отдела.
Карта<Отдел, Длинный> карта = persons.stream().collect(groupingBy(Person::department, counting()));System.out.println(карта);
Вывод программы.
{Department[id=2, name=Finance]=3,Department[id=3, name=ADMIN]=2,Department[id=1, name=HR]=2}
Таким же образом мы можем подсчитать количество всех лиц, имеющих одинаковую зарплату.
Карта<Двойная, длинная> карта = persons.stream().collect(groupingBy(Person::salary, counting()));System.out.println(карта);
Вывод программы.
{800.0=1, 200.0=3, 100.0=1, 900.0=2}
2.4 Группировка по среднему
Можно выполнять и другие агрегированные операции, например, находить среднюю зарплату в каждом отделе.
Карта<Отдел, Двойной> карта = persons.stream().collect(groupingBy(Person::department, averagingDouble(Person::salary)));System.out.println(карта);
Вывод программы.
{Department[id=2, name=Finance]=433.3333333333333,Department[id=3, name=ADMIN]=850.0,Department[id=1, name=HR]=150.0}
2.5 Группировка по Макс/Мин
Чтобы найти максимальное или минимальное значение для сгруппированных значений, используйте коллекторы maxBy() или minBy() и передайте аргумент Comparator для сравнения требуемых значений полей.
В следующем примере мы находим максимально оплачиваемого сотрудника в каждом отделе.
Карта<Отдел, Необязательно<Персона>> map = persons.stream().collect(groupingBy(Person::department, maxBy(Comparator.comparingDouble(Person::salary))));System.out.println(карта);
Вывод программы.
{Department[id=2, name=Finance]=Optional[Person[id=3, name=Charles, salary=900.0,...]],Department[id=3, name=ADMIN]=Optional[Person[id=7, name=George, salary=900.0, ...]],Department[id=1, name=HR]=Optional[Person[id=2, name=Brian, salary=200.0, ...]]}
2.6 Группировка с фильтрацией
Метод Stream.filter() отфильтровывает все несовпадающие элементы из потока перед передачей его в следующую операцию. Это может быть нежелательное решение.
Рассмотрим следующий пример, в котором мы группируем всех лиц по отделу, чья зарплата превышает 300.
Карта<Отдел, Длинный> карта = persons.stream().фильтр(p -> p.зарплата() > 300d).collect(groupingBy(Person::department, counting()));System.out.println(карта);
Вывод программы.
{Department[id=2, name=Finance]=1,Department[id=3, name=ADMIN]=2}
Вышеприведенный вывод программы вообще пропускает department-1, поскольку в этом отделе не было человека, соответствующего условию. Но если мы хотим перечислить все такие ключи Map, где не существует совпадающего значения, то мы можем использовать метод Collectors.filtering(), который применяет фильтр при добавлении значений в Map.
Карта<Отдел, Длинный> карта = persons.stream().collect(groupingBy(Person::department, filtering(p -> p.salary() > 300d, counting())));System.out.println(карта);
Вывод программы. Теперь department-1 также указан с нулевыми совпадающими записями.
{Department[id=2, name=Finance]=1,Department[id=3, name=ADMIN]=2,Department[id=1, name=HR]=0}
3. Заключение
Метод groupBy() Collectors отлично подходит для группировки элементов Stream по различным условиям и выполнения всех видов агрегированных операций над значениями Map. Мы можем использовать комбинации Collectors для выполнения любого вида группировки, как показано в приведенных выше примерах.