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

Модифицируется ли исходная коллекция при создании 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 создана для функционального стиля — читай данные, преобразовывай, возвращай результат. Не меняй исходные данные.