1.8Kпросмотров
25.1%от подписчиков
24 февраля 2026 г.
Score: 1.9K
🔀 Stream.peek: подсматривать в замочную скважину опасно Кажется, что peek() — идеальный способ заглянуть внутрь стрима, посмотреть, что там происходит на каждом этапе, и ничего не сломать. Но Java — язык хитрый: иногда простое подсматривание меняет результат. Заглянул — и сломал. Разбираемся, когда peek() безобиден, а когда начинает колдовать 👇 🟢 Шаг 1: Что такое peek()?
Stream.of("яблоко", "банан", "апельсин") .peek(s -> System.out.println("Обрабатываем: " + s)) .map(String::toUpperCase) .forEach(System.out::println);
➡️ peek() принимает Consumer и выполняет его для каждого элемента, не меняя элемент. Создан для отладки — чтобы смотреть, что течёт по трубе. 🟢 Шаг 2: Когда peek() работает (и это полезно)
List<String> result = Stream.of("one", "two", "three") .peek(System.out::println) // видим все элементы .filter(s -> s.length() > 3) .collect(Collectors.toList());
// Вывод: one, two, three
// Результат: ["three"]
➡️ Пока стрим заканчивается терминальной операцией (здесь collect), peek() выполняется для каждого элемента, проходящего через него. Идеально для быстрой отладки. 🟢 Шаг 3: Ловушка №1 — ленивость стримов
Stream.of("один", "два", "три") .peek(System.out::println) .map(String::length);
// Ничего не выведется! Терминальной операции нет.
➡️ Стримы ленивы. Промежуточные операции (включая peek) не выполняются, пока не вызвана терминальная операция. Если забыть collect или forEach, твой peek молчит — и ты не понимаешь, почему. 🟢 Шаг 4: Ловушка №2 — peek() может изменить состояние (и сломать результат)
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
List<String> result = list.stream() .peek(s -> list.add(s.toUpperCase())) // МОДИФИЦИРУЕМ ИСХОДНЫЙ СПИСОК .collect(Collectors.toList());
System.out.println(result); // ??
➡️ В зависимости от реализации стрима (последовательный/параллельный) это может привести к ConcurrentModificationException или просто к непредсказуемому результату. peek не предназначен для изменения данных — только для чтения. 🟢 Шаг 5: Ловушка №3 — оптимизации стримов
Stream.of("a", "bb", "ccc") .peek(System.out::println) // ожидаем увидеть все? .filter(s -> s.length() > 2) .findFirst(); // терминальная операция, но...
➡️ Вывод: "a", "bb", "ccc"? Нет! Стрим оптимизирует: findFirst() может перестать перебирать элементы, как только найдет подходящий. peek выполняется только для тех, кто реально проходит через него до момента остановки. Здесь: "a" (не прошла фильтр), "bb" (не прошла), "ccc" (прошла — выведется, потом стрим останавливается). Итого вывод только "a", "bb", "ccc". Но если изменить порядок — может быть меньше. 🟢 Шаг 6: Когда peek() безопасен и полезен
🟢 Только для отладки во время разработки.
🟢 Только с терминальной операцией, которая обрабатывает все элементы (например, forEach, collect в список).
🟢 Без изменения внешнего состояния (только чтение, логирование). 🟢 Шаг 7: Чем заменить peek() для реальных действий
// Если нужно логировать + модифицировать — используй map и верни тот же объект
.map(s -> { System.out.println(s); return s; })
➡️ Но это костыль. Лучше использовать полноценный отладчик или логирование вне стрима. 🗣️ Запомни: peek() — это замочная скважина: подсматривать можно, но дверь открывать нельзя. Если нужно что-то сделать с элементами, делай это в map или в терминальной операции.