Промежуточные операции
Промежуточная операция принимает stream и возвращает stream. Она строит pipeline, но обычно не обходит источник до появления терминальной операции.
Основные операции
| Метод | Результат | Характер |
|---|---|---|
filter | оставить элементы по Predicate | stateless |
map | преобразовать один элемент в один | stateless |
flatMap | преобразовать и развернуть вложенные stream | stateless |
mapMulti | передать ноль или несколько результатов в consumer | stateless |
peek | выполнить наблюдающее действие | stateless |
distinct | убрать повторы по equals/hashCode | stateful |
sorted | отсортировать | stateful |
limit, skip | ограничить или пропустить | stateful |
takeWhile, dropWhile | работать с префиксом ordered stream | stateful |
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.
Вопросы для собеседования
- Почему intermediate-операции ленивы? Они только описывают pipeline до terminal.
- Чем stateful отличается от stateless? Stateful хранит данные о других элементах.
- Чем filter отличается от takeWhile? Filter проверяет все элементы, takeWhile берёт только начальный подходящий префикс ordered stream.
- Когда нужен flatMap? Когда один вход даёт вложенный поток результатов.
- Почему sorted опасен на бесконечном stream? Нельзя закончить сортировку.
- Гарантирует ли peek выполнение? Только настолько, насколько элементы реально потреблены pipeline.
- Что использует distinct?
equalsиhashCodeэлементов.