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

Когда изменяются данные в Stream?

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

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

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

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

Когда изменяются данные в Stream?

Основная концепция

Stream в Java - это не контейнер данных, а конвейер обработки. Это ключевой момент: Stream работает с данными на лету (lazily), и изменения в источнике данных могут повлиять на результат в зависимости от того, когда именно операции выполняются.

Терминальные vs Промежуточные операции

Данные в Stream изменяются в момент выполнения терминальной операции.

public class StreamDataFlow {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        
        // Промежуточные операции - НЕ выполняются!
        Stream<Integer> stream = numbers.stream()
            .filter(n -> n > 2)    // Ленивая
            .map(n -> n * 2);      // Ленивая
        
        // Данные ещё НЕ обработаны. Stream в "режиме ожидания"
        System.out.println("Stream created but not executed");
        
        // ТЕРМИНАЛЬНАЯ операция - СЕЙЧАС выполняются все операции!
        List<Integer> result = stream.collect(Collectors.toList());
        System.out.println(result); // [6, 8, 10]
    }
}

Lazy Evaluation (Ленивое вычисление)

Это ключевая характеристика Stream:

public class LazyEvaluation {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        Stream<Integer> stream = numbers.stream()
            .peek(n -> System.out.println("peek: " + n))  // Печатает при выполнении
            .filter(n -> {
                System.out.println("filter: " + n);
                return n > 2;
            })
            .map(n -> {
                System.out.println("map: " + n);
                return n * 2;
            });
        
        System.out.println("Stream created, nothing printed yet!");
        
        // Это вызывает выполнение ВСЕХ операций
        List<Integer> result = stream.collect(Collectors.toList());
        
        // Вывод:
        // Stream created, nothing printed yet!
        // peek: 1
        // filter: 1
        // peek: 2
        // filter: 2
        // peek: 3
        // filter: 3
        // map: 3
        // peek: 4
        // filter: 4
        // map: 4
        // peek: 5
        // filter: 5
        // map: 5
    }
}

Изменения в источнике данных

ВАЖНО: если ты измениш данные в исходном контейнере ПОСЛЕ создания Stream, результат может быть непредсказуем.

public class StreamSourceModification {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        
        // Создаём Stream
        Stream<Integer> stream = numbers.stream()
            .filter(n -> n > 2);
        
        // МОДИФИЦИРУЕМ исходный список ДО терминальной операции
        numbers.add(6);
        numbers.add(7);
        
        // Результат может быть неопределённым!
        List<Integer> result = stream.collect(Collectors.toList());
        System.out.println(result); // Могут быть 6, 7 или нет - undefined behavior!
    }
}

Когда данные читаются из Stream?

public class StreamExecutionTiming {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        
        // СЦЕНАРИЙ 1: Модификация ПОСЛЕ терминальной операции
        List<Integer> result1 = numbers.stream()
            .filter(n -> n > 2)
            .collect(Collectors.toList());
        
        numbers.add(100); // Не повлияет на result1
        System.out.println(result1); // [3, 4, 5]
        
        // СЦЕНАРИЙ 2: Использование Iterator
        Iterator<Integer> iterator = numbers.iterator();
        iterator.next(); // Читает 1
        
        numbers.add(200); // ОПАСНО! Может вызвать ConcurrentModificationException
        iterator.next(); // Может выбросить exception
    }
}

StatefulIntermediateOperations

Некоторые промежуточные операции требуют видеть ВСЕ данные перед обработкой:

public class StatefulOperations {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
        
        // STATEFUL: требует видеть все элементы
        List<Integer> sorted = numbers.stream()
            .sorted()  // Буферизирует ВСЕ элементы
            .limit(3)  // Потом возвращает первые 3
            .collect(Collectors.toList());
        
        System.out.println(sorted); // [1, 1, 2]
        
        // STATELESS: обрабатывает элементы по одному
        List<Integer> filtered = numbers.stream()
            .filter(n -> n > 3)  // Может обработать первый элемент сразу
            .limit(2)
            .collect(Collectors.toList());
    }
}

Проблемы с модификацией

public class StreamProblems {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(Arrays.asList("hello", "world"));
        
        // ❌ НЕПРАВИЛЬНО - может вызвать ConcurrentModificationException
        words.stream()
            .forEach(word -> words.add(word.toUpperCase()));
        
        // ✅ ПРАВИЛЬНО - используй collect
        List<String> newWords = words.stream()
            .flatMap(word -> Stream.of(word, word.toUpperCase()))
            .collect(Collectors.toList());
    }
}

Best Practices

  1. Не модифицируй источник во время работы со Stream
  2. Не полагайся на порядок выполнения промежуточных операций
  3. Используй peek() только для debugging
  4. Помни о Lazy Evaluation - операции выполняются только при терминальной операции
  5. Будь осторожен со stateful операциями (sorted, distinct, skip, limit)

Вывод

Данные в Stream изменяются (обрабатываются) в момент выполнения терминальной операции (collect, forEach, reduce, findFirst и т.д.). Промежуточные операции не выполняются до этого момента, что позволяет эффективно обрабатывать потоки данных и пропускать ненужные вычисления.

Когда изменяются данные в Stream? | PrepBro