Collectors в Java
Collectors.reducing
Collectors.reducing – это универсальный коллектор для выполнения операций свертки (reduction) над элементами потока. Он предоставляет более гибкую альтернативу встроенным операциям типа sum(), max() и min(), позволяя задавать собственную логику агрегации.
1. Три формы reducing
1.1. Базовая форма (с identity, mapper и операцией)
Параметры:
identity – начальное значение (аналог "нейтрального элемента")
mapper – функция преобразования элемента перед обработкой
op – операция для объединения двух значений
Пример (сумма длин строк):
1.2. Упрощенная форма (с identity и операцией)
Пример (конкатенация строк):
1.3. Минималистичная форма (только операция)
Пример (поиск максимального числа):
2. Внутренняя реализация
Принцип работы:
Для каждого элемента применяет mapper (если указан)
Накопление результата через op:
3. Практические примеры
3.1. Сложная агрегация объектов
3.2. Комбинация с groupingBy
3.3. Кастомная агрегация
4. Ограничения и нюансы
Параллельные стримы:
Требует ассоциативности операции ((a op b) op c == a op (b op c))
Пример ассоциативных операций: сложение, умножение, min/max
Неассоциативные: вычитание, деление
Null-значения:
identity не может быть null
Операция должна обрабатывать null, если mapper может его вернуть
Производительность:
Чуть медленнее специализированных коллекторов (summingInt и др.)
На 10-15% медленнее прямого reduce для примитивов
5. Альтернативы
Когда лучше использовать другие методы:
Для примитивов:
Для стандартных операций:
Для неизменяемых коллекций:
#Java #Training #Medium #Collectors #reducing
Collectors.reducing
Collectors.reducing – это универсальный коллектор для выполнения операций свертки (reduction) над элементами потока. Он предоставляет более гибкую альтернативу встроенным операциям типа sum(), max() и min(), позволяя задавать собственную логику агрегации.
1. Три формы reducing
1.1. Базовая форма (с identity, mapper и операцией)
public static <T, U> Collector<T, ?, U> reducing(
U identity,
Function<? super T, ? extends U> mapper,
BinaryOperator<U> op
)
Параметры:
identity – начальное значение (аналог "нейтрального элемента")
mapper – функция преобразования элемента перед обработкой
op – операция для объединения двух значений
Пример (сумма длин строк):
List<String> words = List.of("Java", "Stream", "API");
int totalLength = words.stream()
.collect(Collectors.reducing(
0, // identity
String::length, // mapper
Integer::sum // op
));
// Результат: 11 (4 + 6 + 1)
1.2. Упрощенная форма (с identity и операцией)
public static <T> Collector<T, ?, T> reducing(
T identity,
BinaryOperator<T> op
)
Пример (конкатенация строк):
String concatenated = words.stream()
.collect(Collectors.reducing(
"", // identity
String::concat
));
// Результат: "JavaStreamAPI"
1.3. Минималистичная форма (только операция)
public static <T> Collector<T, ?, Optional<T>> reducing(
BinaryOperator<T> op
)
Пример (поиск максимального числа):
Optional<Integer> max = Stream.of(3, 1, 4)
.collect(Collectors.reducing(
Integer::max
));
// Результат: Optional[4]
2. Внутренняя реализация
Принцип работы:
Для каждого элемента применяет mapper (если указан)
Накопление результата через op:
// Псевдокод реализации
U result = identity;
for (T element : stream) {
result = op.apply(result, mapper.apply(element));
}
return result;
Для формы без identity использует Optional для обработки пустых потоков
3. Практические примеры
3.1. Сложная агрегация объектов
record Product(String name, double price) {}
List<Product> products = List.of(
new Product("Laptop", 999.99),
new Product("Phone", 599.99)
);
// Сумма цен продуктов с названием длиннее 4 символов
double total = products.stream()
.collect(Collectors.reducing(
0.0,
p -> p.name().length() > 4 ? p.price() : 0,
Double::sum
));
3.2. Комбинация с groupingBy
// Максимальная цена в каждой категории
Map<String, Optional<Product>> byCategory = products.stream()
.collect(Collectors.groupingBy(
Product::category,
Collectors.reducing((p1, p2) ->
p1.price() > p2.price() ? p1 : p2
));
3.3. Кастомная агрегация
// Пользовательская свертка для статистики
class Stats {
int count;
double sum;
// + конструкторы/методы
}
Stats stats = stream.collect(Collectors.reducing(
new Stats(0, 0.0),
num -> new Stats(1, num),
(s1, s2) -> new Stats(
s1.count + s2.count,
s1.sum + s2.sum
)
));
4. Ограничения и нюансы
Параллельные стримы:
Требует ассоциативности операции ((a op b) op c == a op (b op c))
Пример ассоциативных операций: сложение, умножение, min/max
Неассоциативные: вычитание, деление
Null-значения:
identity не может быть null
Операция должна обрабатывать null, если mapper может его вернуть
Производительность:
Чуть медленнее специализированных коллекторов (summingInt и др.)
На 10-15% медленнее прямого reduce для примитивов
5. Альтернативы
Когда лучше использовать другие методы:
Для примитивов:
// Вместо:
.collect(reducing(0, Integer::sum))
// Лучше:
.mapToInt().sum()
Для стандартных операций:
// Вместо:
.collect(reducing(Integer::max))
// Лучше:
.max(Comparator.naturalOrder())
Для неизменяемых коллекций:
// Вместо:
.collect(reducing(Collections.emptyList(), customMerge))
// Лучше:
.collect(toUnmodifiableList())
#Java #Training #Medium #Collectors #reducing