Группировка объектов по полю с Collectors
Условие
Дан список транзакций. Сгруппируйте их по типу и посчитайте общую сумму для каждого типа.
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)
Ответ сгенерирован нейросетью и может содержать ошибки
Группировка объектов по полю с 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 — сложная логика обработки данных становится декларативной и легко читаемой.