← Назад к вопросам

В какой список соберутся данные при использовании toList

2.0 Middle🔥 111 комментариев
#Stream API и функциональное программирование#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# toList() в Stream: какой список получится

Вопрос кажется простым, но есть важные детали, которые менялись между версиями Java.

Java 8-15: toList() отсутствует

В старых версиях был только collect(Collectors.toList()):

List<String> list = stream
    .collect(Collectors.toList());  // Возвращает ArrayList

Что возвращалось?ArrayList (изменяемый список)

Java 16+: новый toList()

В Java 16 добавили stream terminal operation toList():

List<String> list = stream.toList();  // ← Новый способ (Java 16+)

ЧТО ВОЗВРАЩАЕТСЯ?Unmodifiable List (неизменяемый!

)

Ключевая разница

// Java 8-15: collect(Collectors.toList())
List<String> list1 = Arrays.stream(new String[]{"a", "b", "c"})
    .collect(Collectors.toList());
list1.add("d");  // ✅ OK — это ArrayList

// Java 16+: toList()
List<String> list2 = Arrays.stream(new String[]{"a", "b", "c"})
    .toList();
list2.add("d");  // ❌ UnsupportedOperationException — неизменяемый!

Почему неизменяемый?

Разработчики JDK решили:
- Если ты используешь Stream (функциональный подход), вероятно хочешь
  неизменяемый список
- Это безопаснее (no side effects)
- Это может быть оптимизировано JVM

Тип, который возвращается toList()

import java.util.stream.Collectors;

List<String> list = Arrays.stream(new String[]{"a", "b", "c"})
    .toList();

// Какой тип?
System.out.println(list.getClass());  
// Output: class java.util.ImmutableCollections$ListN
// или: class java.util.ImmutableCollections$List12
// или: class java.util.ImmutableCollections$ListIter

// Это НЕ ArrayList!
// Это внутренние неизменяемые классы JDK

Сравнение: toList() vs collect(Collectors.toList())

List<String> data = List.of("a", "b", "c");

// collect(Collectors.toList())
List<String> list1 = data.stream()
    .filter(s -> s.length() > 0)
    .collect(Collectors.toList());
list1.add("d");  // ✅ Работает (ArrayList)

// toList() — НОВЫЙ способ (Java 16+)
List<String> list2 = data.stream()
    .filter(s -> s.length() > 0)
    .toList();
list2.add("d");  // ❌ Exception (Unmodifiable)

Неизменяемость

List<String> immutableList = stream.toList();

// ВСЕ ЭТИ ОПЕРАЦИИ ВЫЗЫВАЮТ Exception:
immutableList.add("x");        // UnsupportedOperationException
immutableList.remove(0);       // UnsupportedOperationException
immutableList.set(0, "y");     // UnsupportedOperationException
immutableList.clear();         // UnsupportedOperationException

// Но ЭТА РАБОТАЕТ:
for (String s : immutableList) {
    System.out.println(s);  // ✅ Итерация OK
}

String first = immutableList.get(0);  // ✅ Чтение OK

Nullable элементы

// toList() НЕ позволяет null элементы!
List<String> list = Arrays.asList("a", null, "c").stream()
    .toList();
System.out.println(list);  // [a, null, c]  ← null разрешается

// В отличие от List.of(), который не позволяет null
List<String> list2 = List.of("a", null, "c");  // ❌ NullPointerException

Практические примеры

Пример 1: Когда toList() удобен

// Функциональный код — хочешь неизменяемость
public List<Integer> getNumbers() {
    return numbers.stream()
        .filter(n -> n > 0)
        .map(n -> n * 2)
        .toList();  // ✅ Хорошо — неизменяемый результат
}

// Вызывающий код не может случайно изменить
List<Integer> nums = getNumbers();
nums.add(999);  // ❌ Exception — защита от ошибок

Пример 2: Когда нужен изменяемый список

// Хочешь, чтобы вызывающий код мог добавлять элементы
public List<String> getFilteredItems() {
    return items.stream()
        .filter(this::isValid)
        .collect(Collectors.toList());  // ✅ ArrayList — изменяемый
}

// Вызывающий код может добавлять
List<String> result = getFilteredItems();
result.add("new");  // ✅ OK

Пример 3: С параллельными потоками

List<Integer> numbers = List.of(1, 2, 3, 4, 5);

// Параллельный stream
List<Integer> doubled = numbers.parallelStream()
    .map(n -> n * 2)
    .toList();  // ✅ Thread-safe (неизменяемый)

// Не нужно synchronizedList или какие-то хитрости
// toList() гарантирует, что результат безопасен

Если нужен изменяемый список из toList()

// Способ 1: обернуть в ArrayList
List<String> mutable = new ArrayList<>(immutable.stream()
    .filter(s -> s.length() > 0)
    .toList());
mutable.add("new");  // ✅ OK

// Способ 2: вернуться на collect(Collectors.toList())
List<String> mutable = list.stream()
    .filter(s -> s.length() > 0)
    .collect(Collectors.toList());  // ArrayList
mutable.add("new");  // ✅ OK

// Способ 3: collect с конкретной реализацией
List<String> mutable = list.stream()
    .filter(s -> s.length() > 0)
    .collect(Collectors.toCollection(ArrayList::new));
mutable.add("new");  // ✅ OK

Внутренняя реализация toList()

// Примерно так это выглядит (Java 16+)
public final Stream<T> toList() {
    return this.collect(
        Collectors.collectingAndThen(
            Collectors.toList(),
            Collections::unmodifiableList  // ← Делает неизменяемым
        )
    );
}

Оптимизация: ImmutableCollections

Для маленьких списков JVM может использовать специальные оптимизированные реализации:

List<String> list1 = stream.toList();  // Может быть List0, List1, List2, ...

// Java 16+ использует:
// - List0 для пустого
// - List1 для одного элемента
// - List2 для двух элементов
// - ListN для остальных

// Это более эффективно по памяти, чем ArrayList

Версия Java важна!

// Java 15 и ниже
List<String> list = stream.collect(Collectors.toList());  // ArrayList

// Java 16+
List<String> list = stream.toList();  // Unmodifiable list

// Если код должен работать на Java 15:
List<String> list = stream.collect(Collectors.toList());  // НЕ toList()

Практический совет

// ✅ Используй toList(), если:
- Код на Java 16+
- Хочешь гарантировать неизменяемость
- Функциональный подход (streams)

// ✅ Используй collect(Collectors.toList()), если:
- Код на Java 15 или ниже
- Нужен изменяемый список
- Нужна максимальная совместимость

// ✅ Используй collect(Collectors.toCollection(ArrayList::new)), если:
- Явно хочешь ArrayList
- Не хочешь неизменяемый список

Итог

toList() (Java 16+) возвращает: Unmodifiable List

toList()                  → неизменяемый список (java.util.ImmutableCollections)
collect(Collectors.toList()) → ArrayList (изменяемый)

Это изменение было сделано потому что:

  1. Безопасность: stream results обычно не нужно менять
  2. Performance: неизменяемые могут быть оптимизированы
  3. Modern Java: функциональный стиль предпочитает immutability
В какой список соберутся данные при использовании toList | PrepBro