Промежуточные операции

Промежуточная операция принимает stream и возвращает stream. Она строит pipeline, но обычно не обходит источник до появления терминальной операции.

Основные операции

МетодРезультатХарактер
filterоставить элементы по Predicatestateless
mapпреобразовать один элемент в одинstateless
flatMapпреобразовать и развернуть вложенные streamstateless
mapMultiпередать ноль или несколько результатов в consumerstateless
peekвыполнить наблюдающее действиеstateless
distinctубрать повторы по equals/hashCodestateful
sortedотсортироватьstateful
limit, skipограничить или пропуститьstateful
takeWhile, dropWhileработать с префиксом ordered streamstateful

Stateful-операции могут запоминать уже встреченные элементы или буферизовать часть источника. Это влияет на память и параллельное выполнение.

Filter и map

List<Integer> lengths = words.stream()
        .filter(word -> !word.isBlank())
        .map(String::trim)
        .map(String::length)
        .toList();

Порядок операций имеет значение. Ранний filter сокращает дальнейшую работу, но менять порядок можно только при сохранении смысла.

flatMap и mapMulti

List<String> tags = articles.stream()
        .flatMap(article -> article.tags().stream())
        .distinct()
        .sorted()
        .toList();

flatMap удобен для вложенных коллекций и Optional.stream. mapMulti может избежать создания отдельного маленького stream на каждый элемент:

Stream<Integer> positive = numbers.stream()
        .<Integer>mapMulti((number, out) -> {
            if (number > 0) out.accept(number);
        });

Ограничение потока

limit(n) помогает завершить бесконечный stream. Для ordered stream takeWhile берёт элементы только до первого нарушения условия, а filter проверяет весь доступный источник.

List<Integer> powers = Stream.iterate(1, n -> n * 2)
        .takeWhile(n -> n <= 64)
        .toList();

sorted на бесконечном stream не сможет отдать первый результат: ей нужен весь вход. distinct на бесконечном наборе уникальных значений будет неограниченно наращивать состояние.

Peek

peek предназначен главным образом для наблюдения внутри выполняющегося pipeline. Он ленив и может не выполниться для всех элементов из-за short-circuit или оптимизации. Бизнес-логику и изменение общего состояния в peek помещать нельзя.

long count = orders.stream()
        .filter(Order::paid)
        .peek(order -> logger.debug("paid id={}", order.id()))
        .count();

Порядок

Ordered-источники вроде List сохраняют encounter order, пока операция его не снимает. unordered() разрешает отказаться от порядка и иногда упрощает parallel pipeline. forEachOrdered относится уже к terminal-операциям.

Общее устройство: Stream API. Равенство для distinct: Equals и hashCode. Сортировка: Comparator Java.

Вопросы для собеседования

  1. Почему intermediate-операции ленивы? Они только описывают pipeline до terminal.
  2. Чем stateful отличается от stateless? Stateful хранит данные о других элементах.
  3. Чем filter отличается от takeWhile? Filter проверяет все элементы, takeWhile берёт только начальный подходящий префикс ordered stream.
  4. Когда нужен flatMap? Когда один вход даёт вложенный поток результатов.
  5. Почему sorted опасен на бесконечном stream? Нельзя закончить сортировку.
  6. Гарантирует ли peek выполнение? Только настолько, насколько элементы реально потреблены pipeline.
  7. Что использует distinct? equals и hashCode элементов.