← Назад к вопросам
В какой список соберутся данные при использовании 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 (изменяемый)
Это изменение было сделано потому что:
- Безопасность: stream results обычно не нужно менять
- Performance: неизменяемые могут быть оптимизированы
- Modern Java: функциональный стиль предпочитает immutability