JavaRush /Курсы /JAVA 25 SELF /Преобразование коллекций через Stream

Преобразование коллекций через Stream

JAVA 25 SELF
30 уровень , 4 лекция
Открыта

1. Преобразование List → Set и обратно

Давайте вспомним, зачем вообще нужны преобразования коллекций. Часто в реальных задачах нам нужно:

  • Получить из списка уникальные элементы (например, список e-mail → множество уникальных адресов).
  • Построить отображение (Map), например, из списка имён получить карту «имя → длина имени».
  • Объединить элементы в одну строку (например, для красивого вывода).

Раньше для этого приходилось писать много кода с циклами, условиями и временными коллекциями. Со Stream API всё стало проще и… стильнее!

Пример: получить множество уникальных имён из списка

Допустим, у нас есть список имён (вдруг кто-то в вашей программе ввёл себя два раза — бывает!):

List<String> names = List.of("Анна", "Сергей", "Анна", "Мария", "Иван", "Сергей");

Наша задача — получить коллекцию, где каждое имя встречается только один раз, то есть множество (Set). С помощью Stream API это делается буквально в одну строчку:

Set<String> uniqueNames = names.stream()
    .collect(Collectors.toSet());
System.out.println(uniqueNames);

Вывод:

[Мария, Иван, Анна, Сергей]

(Порядок в Set не гарантируется — не удивляйтесь, если у вас будет другой порядок.)

А если нужно обратно: Set → List?

Иногда нужно наоборот — превратить множество в список (например, чтобы отсортировать или получить доступ по индексу):

List<String> namesList = uniqueNames.stream()
    .collect(Collectors.toList());
System.out.println(namesList);

2. Преобразование в Map: Collectors.toMap()

Пример: из списка имён получить Map «имя → длина имени»

Иногда хочется быть не просто программистом, а настоящим картографом — строить карты! Давайте попробуем:

List<String> names = List.of("Анна", "Сергей", "Мария", "Иван");

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

System.out.println(nameToLength);

Вывод:

{Мария=5, Иван=4, Анна=4, Сергей=6}

Важный момент: дубликаты ключей

Если в исходном списке есть одинаковые имена, то при попытке собрать их в Map возникнет ошибка IllegalStateException: Duplicate key. Java не любит, когда вы пытаетесь положить два значения по одному и тому же ключу.

Как обработать дубликаты?
Можно указать, что делать при совпадении ключей — например, оставить первое значение или последнее:

List<String> names = List.of("Анна", "Сергей", "Анна", "Мария", "Иван", "Сергей");

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

System.out.println(nameToLength);

Теперь программа не упадёт, и в Map попадёт только первое вхождение каждого имени.

Пример: Map с объектами

Давайте немного усложним: у нас есть список пользователей, и мы хотим построить Map «имя → пользователь»:

class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String toString() {
        return name + " (" + age + ")";
    }
}

// Пример списка пользователей
List<User> users = List.of(
    new User("Анна", 25),
    new User("Сергей", 30),
    new User("Мария", 22)
);

Map<String, User> nameToUser = users.stream()
    .collect(Collectors.toMap(
        user -> user.name,
        user -> user
    ));

System.out.println(nameToUser);

Вывод:

{Мария=Мария (22), Анна=Анна (25), Сергей=Сергей (30)}

3. Сборка в строку: Collectors.joining()

Иногда хочется не просто собрать коллекцию, а сделать красивую строку для вывода пользователю или в лог. Например, собрать все имена через запятую:

List<String> names = List.of("Анна", "Сергей", "Мария", "Иван");

String result = names.stream()
    .collect(Collectors.joining(", "));

System.out.println(result);

Вывод:

Анна, Сергей, Мария, Иван

Можно добавить префикс и суффикс

String result = names.stream()
    .collect(Collectors.joining(", ", "Список: [", "]"));

System.out.println(result);

Вывод:

Список: [Анна, Сергей, Мария, Иван]

4. Терминальные операции: forEach, collect, count, anyMatch, allMatch, noneMatch

Метод forEach

С forEach мы уже хорошо знакомы: эта операция выполняет действие для каждого элемента потока.

names.stream().forEach(name -> System.out.println("Привет, " + name + "!"));

Метод collect

Собирает элементы в коллекцию, строку или другую структуру. Самая частая операция — сборка в List или Set с помощью Collectors.toList() и Collectors.toSet().

Метод count

Подсчитывает количество элементов в потоке.

long count = names.stream()
    .filter(name -> name.length() > 4)
    .count();
System.out.println("Имен длиннее 4 букв: " + count);

Методы anyMatch, allMatch, noneMatch

Проверяют, выполняется ли условие хотя бы для одного элемента (anyMatch), для всех (allMatch) или ни для одного (noneMatch).

boolean hasShortName = names.stream()
    .anyMatch(name -> name.length() < 4);
System.out.println("Есть короткое имя? " + hasShortName);

boolean allLong = names.stream()
    .allMatch(name -> name.length() > 3);
System.out.println("Все имена длиннее 3 букв? " + allLong);

boolean noneIvan = names.stream()
    .noneMatch(name -> name.equals("Иван"));
System.out.println("Нет ли Ивана? " + noneIvan);

Вывод:

Есть короткое имя? false
Все имена длиннее 3 букв? true
Нет ли Ивана? false

5. Терминальные и промежуточные операции: закрепим понятия

Промежуточные операции (filter, map, distinct, sorted, limit, skip, peek) — возвращают новый Stream, можно строить цепочки.

Терминальные операции (forEach, collect, count, anyMatch, allMatch, noneMatch, reduce, findFirst, findAny) — завершают поток, результата больше не будет!

Пример цепочки:

List<String> result = users.stream()
    .filter(user -> user.age > 20)
    .map(user -> user.name.toUpperCase())
    .distinct()
    .sorted()
    .collect(Collectors.toList());

System.out.println(result);

Вывод:

[АННА, ИВАН, МАРИЯ, СЕРГЕЙ]

6. Типичные ошибки при преобразовании коллекций через Stream

Ошибка №1: Необработка дубликатов ключей в toMap
Если в исходной коллекции встречаются дублирующиеся ключи, а вы используете Collectors.toMap() без явного обработчика, программа выбросит исключение. Для таких случаев всегда указывайте функцию слияния:

// Оставить последнее значение
.toMap(keyMapper, valueMapper, (oldVal, newVal) -> newVal)

Ошибка №2: Использование forEach вместо collect
Иногда новички пытаются «собрать» коллекцию с помощью forEach, например:

List<String> list = new ArrayList<>();
names.stream().forEach(name -> list.add(name)); // Работает, но это не Stream-way!

Лучше использовать collect(Collectors.toList()) — это безопаснее и чище.

Ошибка №3: Попытка повторно использовать поток
Поток можно использовать только один раз. После терминальной операции (например, collect, forEach) попытка продолжить работать с этим же Stream приведёт к IllegalStateException.

Ошибка №4: Нарушение принципа «без побочных эффектов»
Промежуточные операции должны быть «чистыми» (без изменения внешних переменных). Не стоит внутри map или filter что-то менять вне потока.

Ошибка №5: Неучёт порядка в Set и Map
Если важен порядок элементов, используйте соответствующие коллекции — например, LinkedHashSet, TreeMap — и указывайте нужный коллектор.

1
Задача
JAVA 25 SELF, 30 уровень, 4 лекция
Недоступна
Подсчёт Уникальных Участников Конференции 🧑‍🤝‍🧑
Подсчёт Уникальных Участников Конференции 🧑‍🤝‍🧑
1
Задача
JAVA 25 SELF, 30 уровень, 4 лекция
Недоступна
Формирование Списка Профилей Пользователей с Приоритетом Первой Записи 👤
Формирование Списка Профилей Пользователей с Приоритетом Первой Записи 👤
1
Опрос
Основы Stream API, 30 уровень, 4 лекция
Недоступен
Основы Stream API
Основы Stream API
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
German Malykh Уровень 31
28 декабря 2025

Collectors.toMap(
  K = как получить ключ
  V = как получить значение
  merge = что делать при дублях
  mapSupplier = какую Map создать
)
В Collectors.toMap(...) - По умолчанию используется HashMap Поэтому в задании "Формирование Списка Профилей ...", чтобы сохранить порядок первых появлений пользователей из исходного списка, предлагается передать LinkedHashMap::new. (если не передать mapSupplier, будет обычная Map без гарантии порядка)
Сергей Уровень 42
1 октября 2025
кайф