Що таке операція peek() і коли її використовувати?
Використовується переважно для налагодження:
🟢 Junior Level
peek(Consumer) — це проміжна (intermediate) операція, яка виконує дію над кожним елементом, пропускаючи його далі по ланцюжку.
Використовується переважно для налагодження:
// Подивитися елементи між операціями
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()— terminal-операція (кінець пайплайну) - У якому порядку peek() виводить елементи у parallelStream? — У непередбачуваному порядку: елементи обробляються різними воркерами ForkJoinPool
Червоні прапорці (НЕ говорити):
- «peek() — гарне місце для production-метрик» — може не виконатися; використовуйте map з логуванням
- «peek() гарантує виконання на всіх джерелах» — на SIZED джерелах з count() він пропускається
- «Можна змінювати стан об’єктів у peek()» — це побічний ефект; використовуйте
map()для трансформації - «peek() і forEach() взаємозамінні» — peek проміжний, forEach terminal; різна семантика
Пов’язані теми:
- [[Що таке побічні ефекти (side effects) в Stream]]
- [[Чому слід уникати побічних ефектів в Stream]]
- [[Що таке lazy evaluation в Stream]]
- [[Коли починається виконання операцій в Stream]]