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

Какие знаешь агрегации?

1.0 Junior🔥 101 комментариев
#Базы данных и SQL

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

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

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

Агрегации в обработке потоков и структурах данных

Агрегация — это процесс объединения множества элементов в один результат. В Java это один из ключевых паттернов Stream API и является примером функционального стиля программирования.

Встроенные операции агрегации в Stream API

count() — подсчёт элементов:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream().count(); // 5

sum(), average(), min(), max() — числовые агрегации через IntStream:

int sum = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum(); // 15

Optional<Integer> max = numbers.stream()
    .max(Integer::compareTo);

collect() — универсальная агрегация с Collectors:

// Список -> Множество
Set<Integer> uniqueNumbers = numbers.stream()
    .collect(Collectors.toSet());

// Список -> Строка
String result = Arrays.asList("Java", "Stream", "API")
    .stream()
    .collect(Collectors.joining(", ", "[" , "]")); // [Java, Stream, API]

Collectors — встроенные стратегии агрегации

toList(), toSet(), toCollection() — собрать в коллекцию:

List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

toMap() — преобразование в ассоциативный массив:

Map<Integer, String> idToName = users.stream()
    .collect(Collectors.toMap(User::getId, User::getName));

// С merge function для обработки дубликатов
Map<String, User> byEmail = users.stream()
    .collect(Collectors.toMap(
        User::getEmail, 
        Function.identity(),
        (existing, replacement) -> existing // берём первого
    ));

groupingBy() — группировка элементов по ключу:

// Группировка пользователей по статусу
Map<UserStatus, List<User>> usersByStatus = users.stream()
    .collect(Collectors.groupingBy(User::getStatus));

// Более сложная группировка с подсчётом
Map<String, Long> countByDepartment = users.stream()
    .collect(Collectors.groupingBy(
        User::getDepartment,
        Collectors.counting()
    ));

partitioningBy() — разделение на две группы (true/false):

Map<Boolean, List<Integer>> evenOdd = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {true=[2, 4], false=[1, 3, 5]}

summarizingInt/Long/Double() — получить статистику:

IntSummaryStatistics stats = numbers.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();

System.out.println(stats.getCount());  // 5
System.out.println(stats.getSum());    // 15
System.out.println(stats.getAverage()); // 3.0
System.out.println(stats.getMax());    // 5

Кастомная агрегация с reduce()

reduce() — обобщённая операция объединения элементов:

// Сумма
Optional<Integer> sum = numbers.stream()
    .reduce((a, b) -> a + b);

// С начальным значением
int sumWithIdentity = numbers.stream()
    .reduce(0, (a, b) -> a + b); // 0 + 1 + 2 + 3 + 4 + 5

// Сложная агрегация: объединение объектов
class Summary {
    int count = 0;
    int sum = 0;
}

Summary result = numbers.stream()
    .reduce(
        new Summary(),
        (summary, num) -> {
            summary.count++;
            summary.sum += num;
            return summary;
        },
        (s1, s2) -> { // combiner для параллельных потоков
            s1.count += s2.count;
            s1.sum += s2.sum;
            return s1;
        }
    );

Параллельные агрегации

parallelStream() — использование многоядерности:

long count = hugeList.parallelStream()
    .filter(condition)
    .count();

Внимание: для параллелизма важна идемпотентность и thread-safety функций:

// ❌ Опасно (shared mutable state)
List<Integer> result = new ArrayList<>();
hugeList.parallelStream()
    .forEach(n -> result.add(n * 2)); // race condition!

// ✅ Безопасно (функциональный подход)
List<Integer> result = hugeList.parallelStream()
    .map(n -> n * 2)
    .collect(Collectors.toList());

Агрегация в базах данных (SQL)

В контексте JPA/Hibernate агрегирование часто происходит на уровне БД для производительности:

@Query("SELECT new map(u.department as dept, COUNT(u) as count) " +
       "FROM User u GROUP BY u.department")
List<Map<String, Object>> countByDepartment();

// С Spring Data Specifications
Specification<Order> spec = (root, query, cb) -> {
    query.groupBy(root.get("customerId"));
    return cb.isNotNull(root.get("id"));
};

Практические примеры

Вычисление среднего дохода по категориям:

Map<String, Double> avgIncomeByCategory = orders.stream()
    .collect(Collectors.groupingBy(
        Order::getCategory,
        Collectors.averagingDouble(Order::getPrice)
    ));

Поиск топ-3 самых дорогих товаров:

List<Product> topExpensive = products.stream()
    .sorted(Comparator.comparingDouble(Product::getPrice).reversed())
    .limit(3)
    .collect(Collectors.toList());

Преобразование иерархии в плоский список с агрегацией:

List<String> allTags = articles.stream()
    .flatMap(article -> article.getTags().stream())
    .distinct()
    .collect(Collectors.toList());

Агрегации — один из самых мощных инструментов для обработки данных в Java. Выбор правильного Collector или reduce() стратегии влияет как на читаемость кода, так и на производительность.

Какие знаешь агрегации? | PrepBro