Встречается на 43% собеседований по Java

что такое Stream в Java ?

Stream в Java — это абстракция, представляющая последовательность элементов и позволяющая обрабатывать её в функциональном стиле. API добавлен в Java 8 и находится в пакете java.util.stream. Основная цель — упростить работу с коллекциями, избавив от явных циклов и управления итерациями: фильтрация, сортировка, преобразование и агрегация выполняются декларативно, через цепочки операций.

Как это работает

Stream не хранит данные, а берёт их из источника (коллекция, массив, файл, генератор) и пропускает через конвейер операций. Ключевые особенности:

  • Не изменяет исходные данные — все преобразования возвращают новый поток или результат, исходная коллекция остаётся нетронутой.
  • Ленивая обработка — промежуточные операции не выполняются, пока не вызвана терминальная. Это позволяет оптимизировать вычисления, например, для findFirst() обработается только нужное количество элементов.
  • Параллельность — вызов parallelStream() или parallel() на потоке включает многопоточную обработку, что ускоряет работу на многоядерных системах.

Операции делятся на два типа:

  • Промежуточные (intermediate) — возвращают новый поток, позволяя строить цепочки. Примеры: filter, map, sorted, distinct, limit.
  • Терминальные (terminal) — завершают конвейер и возвращают результат: collect, reduce, count, forEach, findFirst, anyMatch.

Пример

java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "cherry", "date");

        // Фильтрация строк длиннее 5 символов и сбор их длин в список
        List<Integer> lengths = strings.stream()
                .filter(s -> s.length() > 5)   // промежуточная операция
                .map(String::length)           // промежуточная операция
                .collect(Collectors.toList()); // терминальная операция

        System.out.println(lengths); // [6, 6]
    }
}

Здесь stream() создаёт поток из списка, filter оставляет только строки длиннее 5 символов, map преобразует каждую строку в её длину, а collect собирает результат в новый список.

Подводные камни

  • Одноразовость — поток нельзя использовать повторно после терминальной операции; для новой обработки нужно создавать поток заново.
  • Ленивость — без терминальной операции промежуточные не выполняются, поэтому цепочка без collect или forEach просто не сработает.
  • Параллельные потоки — не всегда быстрее: на маленьких данных накладные расходы на синхронизацию могут превысить выигрыш. Используйте parallelStream() осознанно.
  • Побочные эффекты — избегайте изменения внешнего состояния внутри лямбда-выражений, это нарушает функциональный стиль и может привести к ошибкам при параллельном выполнении.
  • null в потокеStream.of(null) выбросит NullPointerException; для работы с nullable-значениями используйте Optional.

Когда использовать

Stream подходит для декларативной обработки коллекций: фильтрация, преобразование, агрегация, группировка. Особенно полезен при работе с большими объёмами данных, когда нужна параллельная обработка, или когда код с циклами становится громоздким. Для простых итераций с побочными эффектами (например, вывод в консоль) обычный цикл for может быть читабельнее.

Коротко

  • Stream — это абстракция последовательности элементов, добавленная в Java 8 для функциональной обработки данных.
  • Промежуточные операции (filter, map, sorted) ленивы и возвращают новый поток; терминальные (collect, reduce, count) запускают вычисления.
  • Поток не изменяет исходные данные и может работать параллельно через parallelStream().
  • Помните об одноразовости потока и избегайте побочных эффектов в лямбда-выражениях.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы