Java for Beginner
717 subscribers
660 photos
174 videos
12 files
1.03K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Collectors в Java

Collectors.toMap

Collectors.toMap – это мощный коллектор, преобразующий элементы потока в Map<K, V>, где ключи и значения вычисляются на основе элементов потока.

1. Базовая форма: toMap(keyMapper, valueMapper)

Преобразует поток в Map, где:
keyMapper – функция, вычисляющая ключ,
valueMapper – функция, вычисляющая значение.


Сигнатура:
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(  
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper
)


Пример:
List<String> names = List.of("Alice", "Bob", "Charlie");  

// Создаем Map: {имя -> длина строки}
Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name, // ключ – сама строка
name -> name.length() // значение – длина строки
));

// Результат: {"Alice":5, "Bob":3, "Charlie":7}
Что произойдет, если ключи повторяются?
→ Будет выброшено IllegalStateException!


2. Обработка дубликатов: toMap(keyMapper, valueMapper, mergeFunction)

Если возможны повторяющиеся ключи, нужно указать mergeFunction – стратегию разрешения конфликтов.

Сигнатура:
public static <T, K, U> Collector<T, ?, Map<K, U>> toMap(  
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction
)


Примеры:

2.1. Оставить старое значение
List<String> names = List.of("Alice", "Bob", "Alice");  

Map<String, Integer> nameToLength = names.stream()
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldValue, newValue) -> oldValue // при конфликте берем старое значение
));

// Результат: {"Alice":5, "Bob":3}


2.2. Объединить значения (например, конкатенация строк)
List<String> names = List.of("A", "B", "A", "C");  

Map<String, String> mergedLetters = names.stream()
.collect(Collectors.toMap(
letter -> letter,
letter -> letter,
(oldVal, newVal) -> oldVal + newVal // "A" + "A" → "AA"
));

// Результат: {"A":"AA", "B":"B", "C":"C"}


2.3. Суммирование значений при дублировании ключей
record Product(String id, double price) {}  

List<Product> products = List.of(
new Product("A", 10.0),
new Product("B", 20.0),
new Product("A", 15.0)
);

Map<String, Double> totalPriceById = products.stream()
.collect(Collectors.toMap(
Product::id,
Product::price,
Double::sum // 10.0 + 15.0 = 25.0
));

// Результат: {"A":25.0, "B":20.0}


3. Выбор реализации Map: toMap(keyMapper, valueMapper, mergeFunction, mapFactory)

Позволяет указать конкретную реализацию Map (например, LinkedHashMap или TreeMap).

Сигнатура:
public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(  
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapFactory
)


Примеры:

3.1. Сохранение порядка вставки (LinkedHashMap)
Map<String, Integer> orderedMap = names.stream()  
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldVal, newVal) -> oldVal,
LinkedHashMap::new
));


3.2. Сортировка по ключу (TreeMap)
Map<String, Integer> sortedMap = names.stream()  
.collect(Collectors.toMap(
name -> name,
name -> name.length(),
(oldVal, newVal) -> oldVal,
TreeMap::new
));


#Java #Training #Medium #Collectors #CollectorsToMap
👍2
4. Особенности и подводные камни

4.1. Null-ключи и Null-значения
HashMap допускает один null-ключ, но если keyMapper возвращает null – будет NullPointerException.
Если valueMapper возвращает null, значение в Map будет null.


4.2. Параллельные стримы
toMap не оптимизирован для параллельных операций.
Вместо него лучше использовать Collectors.toConcurrentMap.


4.3. Изменяемые ключи
Если ключ изменяется после добавления в Map, это может нарушить работу HashMap (ключи должны быть immutable).

5. Альтернативы


5.1. Collectors.toUnmodifiableMap (Java 10+)
Создает неизменяемую Map:

Map<String, Integer> unmodifiableMap = names.stream()  
.collect(Collectors.toUnmodifiableMap(
name -> name,
name -> name.length()
));


5.2. Collectors.toConcurrentMap
Оптимизирован для многопоточности:

ConcurrentMap<String, Integer> concurrentMap = names.stream()  
.collect(Collectors.toConcurrentMap(
name -> name,
name -> name.length()
));


#Java #Training #Medium #Collectors #CollectorsToMap
👍4
Collectors в Java

groupingBy

Collectors.groupingBy – один из самых мощных коллекторов в Java Stream API, позволяющий группировать элементы потока по заданному критерию. Возвращает Map<K, List<T>>, где ключ (K) – это значение классификатора, а значение – список элементов, соответствующих этому ключу.

1. Базовая форма: groupingBy(classifier)

Группирует элементы по ключу, возвращаемому classifier (функцией классификации).

Сигнатура:
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(  
Function<? super T, ? extends K> classifier
)


Пример:
List<String> names = List.of("Alice", "Bob", "Charlie", "Anna", "Alex");  

Map<Integer, List<String>> groupedByLength = names.stream()
.collect(Collectors.groupingBy(String::length));

// Результат:
// {3=[Bob], 4=[Anna, Alex], 5=[Alice], 7=[Charlie]}


Как это работает внутри?

Классификатор (String::length) вычисляет ключ для каждого элемента.
Элементы с одинаковым ключом добавляются в List<T>.
По умолчанию используется HashMap, а значения хранятся в ArrayList.


2. groupingBy(classifier, downstream) – с дополнительным коллектором
Позволяет указать, как собирать элементы в группы (не только в List).

Сигнатура:
public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(  
Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream
)


Примеры:

2.1. Группировка в Set вместо List
Map<Integer, Set<String>> groupedByLength = names.stream()  
.collect(Collectors.groupingBy(
String::length,
Collectors.toSet()
));


2.2. Подсчет количества элементов в каждой группе
Map<Integer, Long> countByLength = names.stream()  
.collect(Collectors.groupingBy(
String::length,
Collectors.counting()
));
// Результат: {3=1, 4=2, 5=1, 7=1}


2.3. Объединение элементов в строку
Map<Integer, String> joinedNames = names.stream()  
.collect(Collectors.groupingBy(
String::length,
Collectors.joining(", ")
));

// Результат: {3="Bob", 4="Anna, Alex", 5="Alice", 7="Charlie"}


3. groupingBy(classifier, mapFactory, downstream) – с выбором реализации Map
Позволяет указать конкретную реализацию Map (например, TreeMap или LinkedHashMap).

Сигнатура:
public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(  
Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream
)


Пример:

// Группировка с сохранением порядка (LinkedHashMap)  
LinkedHashMap<Integer, List<String>> orderedGroups = names.stream()
.collect(Collectors.groupingBy(
String::length,
LinkedHashMap::new,
Collectors.toList()
));

// Группировка с сортировкой по ключу (TreeMap)
TreeMap<Integer, Set<String>> sortedGroups = names.stream()
.collect(Collectors.groupingBy(
String::length,
TreeMap::new,
Collectors.toSet()
));


4. Многокритериальная группировка (вложенные groupingBy)
Можно группировать по нескольким уровням, создавая сложные структуры данных.

Пример:
record Person(String name, String city, int age) {}  

List<Person> people = List.of(
new Person("Alice", "NY", 25),
new Person("Bob", "LA", 30),
new Person("Charlie", "NY", 25),
new Person("David", "LA", 35)
);

// Группировка по городу, а затем по возрасту
Map<String, Map<Integer, List<Person>>> multiLevelGroup = people.stream()
.collect(Collectors.groupingBy(
Person::city,
Collectors.groupingBy(Person::age)
));

// Результат:
// {
// "NY": {25=[Alice, Charlie]},
// "LA": {30=[Bob], 35=[David]}
// }


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

Порядок элементов в группах зависит от исходного потока (если он ordered, как в List, порядок сохраняется).
Хэш-коллизии обрабатываются через HashMap.


Нюансы при параллельных стримах:
Если используется ConcurrentHashMap, группировка становится потокобезопасной.
В некоторых случаях groupingByConcurrent работает быстрее.


#Java #Training #Medium #Collectors #groupingBy
👍3
Collectors в Java

Collectors.summingInt

Collectors.summingInt — это специализированный коллектор, предназначенный для суммирования целочисленных значений, извлекаемых из элементов потока. Он особенно полезен, когда нужно вычислить общую сумму числовых характеристик объектов.

1. Базовая форма и назначение

Сигнатура:
public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper)


Где:

mapper — функция, преобразующая элемент потока в int (значение, которое нужно суммировать)
Возвращает коллектор, который применяет эту функцию ко всем элементам и суммирует результаты


Пример:

List<Product> products = List.of(
new Product("Laptop", 1000),
new Product("Phone", 500),
new Product("Tablet", 300)
);

int totalPrice = products.stream()
.collect(Collectors.summingInt(Product::getPrice));

// Результат: 1800 (1000 + 500 + 300)


2. Внутренняя реализация

Коллектор работает по следующему алгоритму:
Использует промежуточный аккумулятор типа int[] (для мутабельного накопления суммы)
Для каждого элемента применяет mapper и добавляет результат в аккумулятор
В конце возвращает итоговую сумму


3. Особенности и ограничения

3.1. Только для примитивных int

Для long используйте summingLong
Для double — summingDouble


3.2. Нет обработки null

Если mapper возвращает null, будет NullPointerException

3.3. Параллельные стримы

Корректно работает в параллельных стримах благодаря атомарному накоплению

4. Альтернативы

4.1. Через mapToInt() + sum()

Более прямолинейный способ:
int total = products.stream()
.mapToInt(Product::getPrice)
.sum();


Плюсы:

Чуть лучше производительность
Более явное преобразование типов


Минусы:
Нельзя использовать в комбинации с другими коллекторами (например, при группировке)

4.2. reduce()

int total = products.stream()
.reduce(0, (sum, p) -> sum + p.getPrice(), Integer::sum);


5. Комбинирование с другими коллекторами


Особенно полезно в groupingBy для агрегации:

Map<String, Integer> totalPriceByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.summingInt(Product::getPrice)
));


Пример вывода:

{
"Electronics": 1800,
"Office": 750
}


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


Для примитивов (summingInt) работает быстрее, чем reduce с Integer
В HotSpot JIT-компилятор может оптимизировать до прямого сложения


#Java #Training #Medium #Collectors #summingInt
👍2