Что такое операция peek() и когда её использовать?
Используется в основном для отладки:
🟢 Junior Level
peek(Consumer) — это промежуточная операция, которая выполняет действие над каждым элементом, пропуская его дальше по цепочке.
Используется в основном для отладки:
// Посмотреть элементы между операциями
list.stream()
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("After filter: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("After map: " + s))
.collect(Collectors.toList());
В
parallelStream()порядок вывода будет непредсказуемым — элементы обрабатываются разными воркерамиForkJoinPool. В обычномstream()— порядок совпадает с исходным.
Важно: peek() ничего не делает без terminal операции!
🟡 Middle Level
Внутренняя реализация
public void accept(T t) {
action.accept(t); // выполняем действие
downstream.accept(t); // передаём дальше
}
Когда использовать peek()
- Отладка: Посмотреть состояние элементов между
filterиmap - Мониторинг: Замерить метрики без изменения элементов
- Логирование: Записать информацию о подозрительных данных
Не используйте
peekдля production-метрик — в Java 9+ сcount()/findFirst()наSIZEDисточниках код вpeekне выполнится. Используйтеmapс логированием внутри, если мониторинг критичен.
Когда НЕ использовать
- Для побочных эффектов: Изменение внешнего состояния делает стрим хрупким
- Вместо map: Если нужно изменить объект — используйте
map - Вместо forEach: Если нужно действие в конце — используйте
forEach
🔴 Senior Level
Ловушка оптимизации (Java 9+)
Стрим может вообще не вызвать peek():
long count = Stream.of("A", "B", "C")
.peek(System.out::println) // Может не выполниться!
.count();
В Java 9+ метод count() оптимизирован: если источник имеет SIZED характеристику, стрим просто вернет размер, не прогоняя элементы через конвейер. SIZED = ArrayList, массив, List.of() — известен точный размер. Не-SIZED = Stream.generate(), Iterator, Stream.iterate() — размер неизвестен заранее. Оптимизация появилась в Java 9 и сохранилась в 11, 17, 21.
Следствие: Если засунули бизнес-логику в peek — она будет пропущена.
Production Safety
В продакшн-коде peek должен встречаться крайне редко. Если видите его часто — кто-то использует стримы как “навороченные циклы”.
Performance
Каждый peek добавляет один вызов метода в цепочку для каждого элемента. В Highload это лишние такты CPU.
Диагностика
Вместо peek(System.out::println) используйте встроенный Stream Debugger в IntelliJ IDEA — позволяет смотреть состояние на каждом этапе без модификации кода.
🎯 Шпаргалка для интервью
Обязательно знать:
peek(Consumer)— промежуточная операция, выполняет действие над элементом и передаёт его дальше- Основное назначение — отладка: просмотр элементов между
filter,map,sorted - Без terminal операции
peek()ничего не делает — стрим ленивый - В Java 9+
peek()может не выполниться наSIZEDисточниках сcount()— JVM оптимизирует пайплайн, пропуская весь конвейер - SIZED источники:
ArrayList, массив,List.of(). Не-SIZED:Stream.generate(),Iterator,LinkedList - Не используйте
peekдля production-метрик — в некоторых случаях код вpeekне выполнится - Каждый
peekдобавляет вызов метода на каждый элемент — в Highload это лишние такты CPU
Частые уточняющие вопросы:
- Почему peek() не выполняется в Java 9+ с count()? —
count()на SIZED источнике оптимизирован: JVM возвращает размер, не прогоняя элементы через конвейер - Можно ли использовать peek() для бизнес-логики? — Категорически нет; он может быть пропущен оптимизацией. Используйте
map()с логированием внутри - Чем peek() отличается от forEach()? —
peek()— промежуточная операция (передаёт элемент дальше),forEach()— терминальная (конец пайплайна) - В каком порядке peek() выводит элементы в parallelStream? — В непредсказуемом порядке: элементы обрабатываются разными воркерами ForkJoinPool
Красные флаги (НЕ говорить):
- «peek() — хорошее место для production-метрик» — может не выполниться; используйте map с логированием
- «peek() гарантирует выполнение на всех источниках» — на SIZED источниках с count() он пропускается
- «Можно менять состояние объектов в peek()» — это побочный эффект; используйте
map()для трансформации - «peek() и forEach() взаимозаменяемы» — peek промежуточный, forEach терминальный; разная семантика
Связанные темы:
- [[Что такое побочные эффекты (side effects) в Stream]]
- [[Почему следует избегать побочных эффектов в Stream]]
- [[Что такое lazy evaluation в Stream]]
- [[Когда начинается выполнение операций в Stream]]