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

Группировка объектов по полю с Collectors

1.0 Junior🔥 101 комментариев
#Другое#Основы Java

Условие

Дан список транзакций. Сгруппируйте их по типу и посчитайте общую сумму для каждого типа.

class Transaction {
    String type; // "DEPOSIT", "WITHDRAWAL", "TRANSFER"
    double amount;
}

Пример

Вход:

  • DEPOSIT: 100, 200
  • WITHDRAWAL: 50
  • TRANSFER: 150, 100

Выход: {DEPOSIT=300.0, WITHDRAWAL=50.0, TRANSFER=250.0}

Требования

  • Используйте Collectors.groupingBy()
  • Используйте Collectors.summingDouble()
  • Решение в одну строку

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

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

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

Группировка объектов по полю с Collectors

Эта задача демонстрирует один из самых полезных паттернов в Stream API — группировку данных по ключам с агрегацией значений. Это идеален для обработки различных бизнес-сценариев: финансовые отчёты, аналитика, трансформация данных.

Решение

class Transaction {
    String type;    // "DEPOSIT", "WITHDRAWAL", "TRANSFER"
    double amount;
    
    public Transaction(String type, double amount) {
        this.type = type;
        this.amount = amount;
    }
    
    public String getType() {
        return type;
    }
    
    public double getAmount() {
        return amount;
    }
}

public class TransactionAnalyzer {
    public static void main(String[] args) {
        List<Transaction> transactions = Arrays.asList(
            new Transaction("DEPOSIT", 100),
            new Transaction("DEPOSIT", 200),
            new Transaction("WITHDRAWAL", 50),
            new Transaction("TRANSFER", 150),
            new Transaction("TRANSFER", 100)
        );
        
        // Решение в одну строку
        Map<String, Double> summary = transactions.stream()
            .collect(Collectors.groupingBy(
                Transaction::getType,
                Collectors.summingDouble(Transaction::getAmount)
            ));
        
        System.out.println(summary);
        // Вывод: {WITHDRAWAL=50.0, DEPOSIT=300.0, TRANSFER=250.0}
    }
}

Объяснение каждой части

1. transactions.stream()

Преобразует List в Stream для обработки:

stream() // Берём каждую транзакцию по одной

2. .collect(Collectors.groupingBy(...))

Это главный collector, который группирует элементы. Принимает функцию-классификатор:

Collectors.groupingBy(
    Transaction::getType  // Извлекаем поле type как ключ
)

Результат без агрегации был бы:

{
    DEPOSIT: [Transaction(100), Transaction(200)],
    WITHDRAWAL: [Transaction(50)],
    TRANSFER: [Transaction(150), Transaction(100)]
}

3. Collectors.summingDouble(...)

Этот вложенный collector агрегирует значения в каждой группе:

Collectors.summingDouble(
    Transaction::getAmount  // Суммируем значения amount
)

Теперь вместо списков объектов получаем сумму:

{
    DEPOSIT: 100 + 200 = 300.0,
    WITHDRAWAL: 50.0,
    TRANSFER: 150 + 100 = 250.0
}

Альтернативные способы агрегации

Подсчёт элементов в группе:

Map<String, Long> counts = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.counting()  // Вместо summingDouble
    ));
// {WITHDRAWAL=1, DEPOSIT=2, TRANSFER=2}

Среднее значение:

Map<String, Double> averages = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.averagingDouble(Transaction::getAmount)
    ));
// {WITHDRAWAL=50.0, DEPOSIT=150.0, TRANSFER=125.0}

Статистика (min, max, sum, count, average):

Map<String, DoubleSummaryStatistics> stats = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.summarizingDouble(Transaction::getAmount)
    ));

stats.forEach((type, stat) -> 
    System.out.println(type + ": sum=" + stat.getSum() + 
                       ", avg=" + stat.getAverage())
);

Сбор в список:

Map<String, List<Transaction>> byType = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.toList()  // Все транзакции в списке
    ));

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

Если нужна группировка по двум полям — используйте вложенные groupingBy:

class Transaction {
    String type;
    String currency;  // "USD", "EUR"
    double amount;
    // ...
}

// Группируем сначала по типу, потом по валюте
Map<String, Map<String, Double>> grouped = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.groupingBy(
            Transaction::getCurrency,
            Collectors.summingDouble(Transaction::getAmount)
        )
    ));

// {DEPOSIT={USD=300.0, EUR=150.0}, TRANSFER={USD=250.0}}

Фильтрация перед группировкой

// Группируем только депозиты
Map<String, Double> depositsOnly = transactions.stream()
    .filter(t -> t.getType().equals("DEPOSIT"))  // Фильтруем
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.summingDouble(Transaction::getAmount)
    ));
// {DEPOSIT=300.0}

Обработка после группировки

// Отсортировать результат по сумме
Map<String, Double> sorted = transactions.stream()
    .collect(Collectors.groupingBy(
        Transaction::getType,
        Collectors.summingDouble(Transaction::getAmount)
    ))
    .entrySet().stream()
    .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
    .collect(Collectors.toMap(
        Map.Entry::getKey,
        Map.Entry::getValue,
        (a, b) -> a,
        LinkedHashMap::new
    ));
// {DEPOSIT=300.0, TRANSFER=250.0, WITHDRAWAL=50.0}

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

Collectors.groupingBy() оптимален для:

  • Среднее количество групп (< 1000)
  • Размер потока < 100,000 элементов

Для больших объёмов рассмотрите:

  • Параллельные потоки: .parallelStream()
  • Собственную оптимизацию с ConcurrentHashMap

Итоговое решение (полная версия)

import java.util.*;
import java.util.stream.Collectors;

class Transaction {
    private String type;
    private double amount;
    
    public Transaction(String type, double amount) {
        this.type = type;
        this.amount = amount;
    }
    
    public String getType() { return type; }
    public double getAmount() { return amount; }
}

public class TransactionGrouping {
    public static void main(String[] args) {
        List<Transaction> transactions = Arrays.asList(
            new Transaction("DEPOSIT", 100),
            new Transaction("DEPOSIT", 200),
            new Transaction("WITHDRAWAL", 50),
            new Transaction("TRANSFER", 150),
            new Transaction("TRANSFER", 100)
        );
        
        // Решение: одна строка благодаря Stream API
        Map<String, Double> summary = transactions.stream()
            .collect(Collectors.groupingBy(
                Transaction::getType,
                Collectors.summingDouble(Transaction::getAmount)
            ));
        
        // Красивый вывод
        summary.forEach((type, sum) -> 
            System.out.println(type + ": " + sum)
        );
        // DEPOSIT: 300.0
        // WITHDRAWAL: 50.0
        // TRANSFER: 250.0
    }
}

Это решение демонстрирует мощь Stream API — сложная логика обработки данных становится декларативной и легко читаемой.