Collectors в Java
Collectors.partitioningBy
Collectors.partitioningBy – это специальный коллектор, который разделяет элементы потока на две группы с помощью предиката.
Результатом всегда является Map<Boolean, List<T>>, где:
true – элементы, удовлетворяющие условию
false – элементы, не удовлетворяющие условию
1. Базовая форма: partitioningBy(predicate)
Разделяет элементы на две группы без дополнительной обработки.
Сигнатура:
Пример:
Особенности:
Всегда возвращает обе группы (даже если одна пустая)
Сохраняет порядок элементов, если поток упорядочен
2. Расширенная форма: partitioningBy(predicate, downstream)
Позволяет дополнительно обработать каждую группу с помощью другого коллектора.
Сигнатура:
Примеры применения:
2.1. Подсчет количества элементов в каждой группе
2.2. Объединение элементов в строку
2.3. Группировка в Set вместо List
3. Внутренняя реализация
Коллектор работает по следующему алгоритму:
Создает Map<Boolean, List<T>> с двумя ключами (true/false)
Для каждого элемента применяет предикат и добавляет в соответствующую группу
Если указан downstream-коллектор, применяет его к каждой группе
Псевдокод реализации:
4. Практические примеры
4.1. Разделение пользователей по возрасту
4.2. Статистика по разделенным группам
5. Ограничения и нюансы
Неизменяемость результатов:
Возвращаемые коллекции можно модифицировать (обычно ArrayList)
Для неизменяемых групп использовать collectingAndThen:
Параллельные стримы:
Работает корректно в параллельных стримах
Группы объединяются через Map.merge
Null-значения:
Предикат должен обрабатывать null (иначе NPE)
Решение:
#Java #Training #Medium #Collectors #partitioningBy
Collectors.partitioningBy
Collectors.partitioningBy – это специальный коллектор, который разделяет элементы потока на две группы с помощью предиката.
Результатом всегда является Map<Boolean, List<T>>, где:
true – элементы, удовлетворяющие условию
false – элементы, не удовлетворяющие условию
1. Базовая форма: partitioningBy(predicate)
Разделяет элементы на две группы без дополнительной обработки.
Сигнатура:
public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(
Predicate<? super T> predicate
)
Пример:
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// Результат:
// {
// false=[1, 3, 5],
// true=[2, 4]
// }
Особенности:
Всегда возвращает обе группы (даже если одна пустая)
Сохраняет порядок элементов, если поток упорядочен
2. Расширенная форма: partitioningBy(predicate, downstream)
Позволяет дополнительно обработать каждую группу с помощью другого коллектора.
Сигнатура:
public static <T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(
Predicate<? super T> predicate,
Collector<? super T, A, D> downstream
)
Примеры применения:
2.1. Подсчет количества элементов в каждой группе
Map<Boolean, Long> countByPartition = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.counting()
));
// Результат: {false=3, true=2}
2.2. Объединение элементов в строку
Map<Boolean, String> joinedPartitions = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.mapping(
Object::toString,
Collectors.joining("-")
)
));
// Результат: {false="1-3-5", true="2-4"}
2.3. Группировка в Set вместо List
Map<Boolean, Set<Integer>> toSet = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.toSet()
));
3. Внутренняя реализация
Коллектор работает по следующему алгоритму:
Создает Map<Boolean, List<T>> с двумя ключами (true/false)
Для каждого элемента применяет предикат и добавляет в соответствующую группу
Если указан downstream-коллектор, применяет его к каждой группе
Псевдокод реализации:
Map<Boolean, A> result = new HashMap<>();
result.put(true, new ArrayList<>());
result.put(false, new ArrayList<>());
stream.forEach(item -> {
boolean key = predicate.test(item);
result.get(key).add(item);
});
// Если есть downstream:
result.replaceAll((k, v) -> downstream.finisher().apply(v));
4. Практические примеры
4.1. Разделение пользователей по возрасту
record Person(String name, int age) {}
List<Person> people = List.of(
new Person("Alice", 25),
new Person("Bob", 17),
new Person("Charlie", 30)
);
Map<Boolean, List<Person>> adults = people.stream()
.collect(Collectors.partitioningBy(p -> p.age() >= 18));
// Результат:
// {
// false=[Person[name=Bob, age=17]],
// true=[Person[name=Alice, age=25], Person[name=Charlie, age=30]]
// }
4.2. Статистика по разделенным группам
Map<Boolean, IntSummaryStatistics> stats = numbers.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0,
Collectors.summarizingInt(Integer::intValue)
));
// Получить среднее для четных чисел:
double avgEven = stats.get(true).getAverage();
5. Ограничения и нюансы
Неизменяемость результатов:
Возвращаемые коллекции можно модифицировать (обычно ArrayList)
Для неизменяемых групп использовать collectingAndThen:
.collect(Collectors.collectingAndThen(
Collectors.partitioningBy(predicate),
Collections::unmodifiableMap
));
Параллельные стримы:
Работает корректно в параллельных стримах
Группы объединяются через Map.merge
Null-значения:
Предикат должен обрабатывать null (иначе NPE)
Решение:
.partitioningBy(item -> item != null && predicate.test(item))
#Java #Training #Medium #Collectors #partitioningBy
Collectors в Java
Collectors.collectingAndThen
Collectors.collectingAndThen – это особый коллектор, который добавляет финальное преобразование к результату другого коллектора. Это мощный инструмент для пост-обработки данных после сбора.
1. Базовая концепция
Сигнатура:
Где:
downstream – основной коллектор (например, toList, groupingBy)
finisher – функция, применяемая к результату коллектора
RR – новый тип возвращаемого значения
2. Простые примеры
2.1. Создание неизменяемого списка
2.2. Получение первого элемента
3. Комбинация с другими коллекторами
3.1. Группировка с неизменяемыми значениями
3.2. Подсчет с дополнительной проверкой
4. Внутренняя реализация
Принцип работы:
Сначала выполняется основной коллектор (downstream)
Затем к его результату применяется finisher
Возвращается преобразованное значение
Псевдокод:
5. Практические применения
5.1. Безопасное извлечение Optional
5.2. Нормализация данных после группировки
6. Особенности и ограничения
Порядок выполнения:
Сначала полностью выполняется downstream-коллектор
Затем применяется finisher
Параллельные стримы:
Работает корректно, если finisher – thread-safe
Null-обработка:
Finisher должен сам обрабатывать null-значения
7. Производительность
Обычно добавляет незначительные накладные расходы:
5-10% к времени выполнения базового коллектора
В HotSpot часто инлайнится JIT-компилятором
#Java #Training #Medium #Collectors #partitioningBy
Collectors.collectingAndThen
Collectors.collectingAndThen – это особый коллектор, который добавляет финальное преобразование к результату другого коллектора. Это мощный инструмент для пост-обработки данных после сбора.
1. Базовая концепция
Сигнатура:
public static <T, A, R, RR> Collector<T, A, RR> collectingAndThen(
Collector<T, A, R> downstream,
Function<R, RR> finisher
)
Где:
downstream – основной коллектор (например, toList, groupingBy)
finisher – функция, применяемая к результату коллектора
RR – новый тип возвращаемого значения
2. Простые примеры
2.1. Создание неизменяемого списка
List<String> immutableList = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
Collections::unmodifiableList
));
2.2. Получение первого элемента
String firstItem = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> list.isEmpty() ? null : list.get(0)
);
3. Комбинация с другими коллекторами
3.1. Группировка с неизменяемыми значениями
Map<String, List<Integer>> unmodifiableGroups = numbers.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
n -> n % 2 == 0 ? "even" : "odd"
),
Collections::unmodifiableMap
));
3.2. Подсчет с дополнительной проверкой
Long countOrZero = stream
.collect(Collectors.collectingAndThen(
Collectors.counting(),
c -> c > 100 ? c : 0L
));
4. Внутренняя реализация
Принцип работы:
Сначала выполняется основной коллектор (downstream)
Затем к его результату применяется finisher
Возвращается преобразованное значение
Псевдокод:
R intermediateResult = downstream.collect(stream);
RR finalResult = finisher.apply(intermediateResult);
return finalResult;
5. Практические применения
5.1. Безопасное извлечение Optional
Optional<String> lastElement = stream
.collect(Collectors.collectingAndThen(
Collectors.toList(),
list -> list.isEmpty()
? Optional.empty()
: Optional.of(list.get(list.size()-1))
);
5.2. Нормализация данных после группировки
Map<String, Set<String>> caseInsensitiveGroups = words.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
String::toLowerCase,
Collectors.toSet()
),
Collections::unmodifiableMap
));
6. Особенности и ограничения
Порядок выполнения:
Сначала полностью выполняется downstream-коллектор
Затем применяется finisher
Параллельные стримы:
Работает корректно, если finisher – thread-safe
Null-обработка:
Finisher должен сам обрабатывать null-значения
7. Производительность
Обычно добавляет незначительные накладные расходы:
5-10% к времени выполнения базового коллектора
В HotSpot часто инлайнится JIT-компилятором
#Java #Training #Medium #Collectors #partitioningBy