← Назад к вопросам
Модифицируется ли исходная коллекция при создании Stream
2.3 Middle🔥 111 комментариев
#Stream API и функциональное программирование#Коллекции
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Модифицируется ли исходная коллекция при создании Stream
Краткий ответ: НЕТ. Создание Stream не изменяет исходную коллекцию. Однако есть важные нюансы, которые нужно понимать.
1. Stream НЕ создаёт копию
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Создание Stream НЕ копирует список
Stream<Integer> stream = numbers.stream();
// numbers остаётся без изменений
System.out.println(numbers); // [1, 2, 3, 4, 5]
Stream делегирует доступ к исходной коллекции, но не изменяет её.
2. Изменение исходной коллекции во время обработки Stream — опасно
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
try {
numbers.stream()
.forEach(n -> {
System.out.println("Processing: " + n);
// ОПАСНО! Изменять коллекцию во время итерации
if (n == 3) {
numbers.remove(Integer.valueOf(4));
}
});
} catch (ConcurrentModificationException e) {
System.out.println("Ошибка: " + e.getMessage());
}
// Результат: ConcurrentModificationException
// Stream обнаружит что коллекция была изменена
3. Операции Map/Filter НЕ изменяют исходную коллекцию
List<Integer> original = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
System.out.println("До: " + original); // [1, 2, 3, 4, 5]
// map() НЕ изменяет original
List<Integer> doubled = original.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println("После map: " + original); // [1, 2, 3, 4, 5] — не изменился
System.out.println("Результат: " + doubled); // [2, 4, 6, 8, 10]
// filter() тоже НЕ изменяет original
List<Integer> evens = original.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("После filter: " + original); // [1, 2, 3, 4, 5] — не изменился
System.out.println("Результат: " + evens); // [2, 4]
4. Проблема: элементы — это ссылки
Если коллекция содержит объекты, изменение этих объектов отражается везде:
public class User {
public String name;
public int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
List<User> users = new ArrayList<>();
users.add(new User("Alice", 25));
users.add(new User("Bob", 30));
// Stream НЕ создаёт копию объектов
users.stream()
.forEach(user -> user.age++); // Изменяем объекты
// Исходный список БЫЛ изменён (объекты изменены)
users.forEach(u -> System.out.println(u.name + ": " + u.age));
// Результат:
// Alice: 26
// Bob: 31
// НО сам список остался тем же (размер, порядок не изменились)
5. Безопасный подход: создать копию
List<Integer> original = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// Способ 1: Копировать в терминальной операции
List<Integer> result = original.stream()
.collect(Collectors.toList()); // Новый список
// Способ 2: Если нужна неизменяемая копия
List<Integer> immutable = original.stream()
.collect(Collectors.toUnmodifiableList());
// Способ 3: Копировать элементы явно
List<Integer> copy = new ArrayList<>(original);
6. Изменение структуры коллекции во время Stream
List<String> names = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie"));
// ОПАСНО и может привести к undefined поведению
try {
names.stream()
.filter(name -> !name.equals("Bob"))
.forEach(name -> {
// Изменение структуры коллекции запрещено
names.remove(name); // ConcurrentModificationException
});
} catch (ConcurrentModificationException e) {
System.out.println("Ошибка: коллекция была изменена во время итерации");
}
// Правильно: сначала собрать результат, потом изменить
List<String> toRemove = names.stream()
.filter(name -> name.equals("Bob"))
.collect(Collectors.toList());
names.removeAll(toRemove);
System.out.println(names); // [Alice, Charlie]
7. Parallel Stream и потокобезопасность
List<Integer> numbers = Collections.synchronizedList(
new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5))
);
// Parallel stream может улучшить производительность
List<Integer> result = numbers.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());
// Исходный список всё ещё не изменён
System.out.println(numbers); // [1, 2, 3, 4, 5]
System.out.println(result); // [2, 4, 6, 8, 10]
// ВАЖНО: не изменяй исходную коллекцию в parallelStream
// потокобезопасность усложнит код
Правила использования Stream
✓ МОЖНО:
- Читать элементы из коллекции
- Преобразовывать элементы (map, filter)
- Создавать новые коллекции (collect)
- Вычислять результаты (reduce, count)
✗ НЕ МОЖНО:
- Изменять структуру коллекции во время Stream
- Удалять элементы
- Добавлять элементы
- Полагаться на побочные эффекты в map/filter
✓ БЕЗОПАСНО:
- Если элемент = immutable (Integer, String, LocalDate)
- Если не изменяешь коллекцию
Практический пример
public class OrderProcessor {
// Безопасный код
public List<OrderDTO> processOrders(List<Order> orders) {
// Stream НЕ изменяет orders
return orders.stream()
.filter(order -> order.isValid())
.map(this::convertToDTO)
.collect(Collectors.toList()); // Новый список
}
// Опасный код — НЕ ДЕЛАЙ ТАК
public void dangerousProcessing(List<Order> orders) {
orders.stream()
.filter(order -> order.isValid())
.forEach(orders::remove); // ConcurrentModificationException!
}
}
Вывод
- Создание Stream НЕ изменяет исходную коллекцию
- Операции (map, filter) НЕ изменяют исходную коллекцию
- Итерация (forEach) НЕ изменяет размер коллекции
- ОПАСНО: изменять коллекцию во время обработки Stream
- БЕЗОПАСНО: создать новую коллекцию через
collect()
Stream создана для функционального стиля — читай данные, преобразовывай, возвращай результат. Не меняй исходные данные.