← Назад к вопросам

Как сгруппировать элементы используя Stream

2.0 Middle🔥 141 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как сгруппировать элементы используя Stream

groupingBy() — это мощный collector в Stream API для группировки элементов по ключу. Это аналог SQL GROUP BY.

Базовая группировка

List<String> fruits = Arrays.asList("apple", "banana", "apricot", "blueberry");

Map<Character, List<String>> grouped = fruits.stream()
    .collect(Collectors.groupingBy(s -> s.charAt(0)));

// Результат:
// {a=[apple, apricot], b=[banana, blueberry]}

Группировка объектов

public class Person {
    private String name;
    private int age;
    private String department;
    
    // getters, setters
}

List<Person> people = Arrays.asList(
    new Person("Alice", 30, "IT"),
    new Person("Bob", 30, "HR"),
    new Person("Charlie", 25, "IT")
);

// Группируем по отделу
Map<String, List<Person>> byDept = people.stream()
    .collect(Collectors.groupingBy(Person::getDepartment));

// {IT=[Alice, Charlie], HR=[Bob]}

Подсчёт элементов

// Количество людей в каждом отделе
Map<String, Long> deptCount = people.stream()
    .collect(Collectors.groupingBy(
        Person::getDepartment,
        Collectors.counting()
    ));

// {IT=2, HR=1}

Суммирование

public class Product {
    private String category;
    private double price;
    // getters, setters
}

List<Product> products = Arrays.asList(
    new Product("Electronics", 1000),
    new Product("Books", 20),
    new Product("Electronics", 500)
);

// Сумма цены по категориям
Map<String, Double> totalByCategory = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.summingDouble(Product::getPrice)
    ));

// {Electronics=1500.0, Books=20.0}

Усреднение

// Средняя цена по категориям
Map<String, Double> avgByCategory = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.averagingDouble(Product::getPrice)
    ));

// {Electronics=750.0, Books=20.0}

Многоуровневая группировка

public class Employee {
    private String name;
    private String department;
    private String position;
    // getters
}

List<Employee> employees = Arrays.asList(
    new Employee("Alice", "IT", "Developer"),
    new Employee("Bob", "IT", "Manager"),
    new Employee("Charlie", "HR", "Developer")
);

// Группируем сначала по отделу, потом по должности
Map<String, Map<String, List<Employee>>> nested = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.groupingBy(Employee::getPosition)
    ));

// {IT={Developer=[Alice], Manager=[Bob]}, HR={Developer=[Charlie]}}

Строка вместо List

// Вместо списка объектов, получаем строку
Map<String, String> namesByDept = people.stream()
    .collect(Collectors.groupingBy(
        Person::getDepartment,
        Collectors.mapping(
            Person::getName,
            Collectors.joining(", ")
        )
    ));

// {IT=Alice, Charlie, HR=Bob}

Множество элементов (Set)

// Вместо List получаем Set
Map<String, Set<Person>> deptSet = people.stream()
    .collect(Collectors.groupingBy(
        Person::getDepartment,
        Collectors.toSet()
    ));

Статистика

import java.util.IntSummaryStatistics;

// Статистика по зарплатам в отделах
Map<String, IntSummaryStatistics> stats = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.summarizingInt(Employee::getSalary)
    ));

IntSummaryStatistics itStats = stats.get("IT");
System.out.println("Count: " + itStats.getCount());
System.out.println("Sum: " + itStats.getSum());
System.out.println("Average: " + itStats.getAverage());
System.out.println("Min: " + itStats.getMin());
System.out.println("Max: " + itStats.getMax());

Пользовательский collector

// Группируем и затем преобразуем результат
Map<String, Integer> countOfExpensive = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.collectingAndThen(
            Collectors.filtering(
                p -> p.getPrice() > 100,
                Collectors.toList()
            ),
            List::size
        )
    ));

Условная группировка

// Группируем по условию (есть/нет)
Map<Boolean, List<Product>> expensive = products.stream()
    .collect(Collectors.partitioningBy(p -> p.getPrice() > 500));

// {false=[Books], true=[Electronics, Electronics]}

Производительность

// Для больших данных лучше использовать параллельный Stream
Map<String, Long> result = products.parallelStream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.counting()
    ));

Сложный пример: Top N элементов по группе

import java.util.stream.Collectors;

// Берём top 2 самых дорогих товара в каждой категории
Map<String, List<Product>> topByCategory = products.stream()
    .collect(Collectors.groupingBy(
        Product::getCategory,
        Collectors.collectingAndThen(
            Collectors.toList(),
            list -> list.stream()
                .sorted(Comparator.comparingDouble(Product::getPrice).reversed())
                .limit(2)
                .collect(Collectors.toList())
        )
    ));

Ключевые моменты

  1. groupingBy(keyMapper) — базовая группировка
  2. groupingBy(keyMapper, collector) — группировка с кастомным collector
  3. groupingBy(keyMapper, supplier, collector) — с кастомной Map
  4. Множественные уровни — nested groupingBy
  5. Collectors.counting() — количество элементов
  6. Collectors.summingInt/Long/Double() — сумма
  7. Collectors.averaging()* — среднее значение
  8. Collectors.joining() — объединение строк
  9. Collectors.toSet() — Set вместо List
  10. parallelStream() — параллельная обработка

Stream groupingBy — это мощный инструмент для трансформации и анализа данных с минимальным кодом.