← Назад к вопросам
Что делает peek в Stream API?
1.2 Junior🔥 111 комментариев
#Stream API и функциональное программирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# peek() в Stream API
peek() — это промежуточная операция в Stream API, которая позволяет выполнить действие для каждого элемента потока БЕЗ изменения самого потока. Обычно используется для отладки и логирования.
1. Определение
public Stream<T> peek(Consumer<? super T> action)
Параметры:
action— функция (Consumer), которая будет вызвана для каждого элемента
Возвращает:
- Новый Stream с тем же содержимым
Тип операции: промежуточная (lazy)
2. Синтаксис
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("Processing: " + n)) // Отладка
.filter(n -> n % 2 == 0) // Фильтрация
.peek(n -> System.out.println("After filter: " + n)) // Отладка
.map(n -> n * 2) // Трансформация
.peek(n -> System.out.println("After map: " + n)) // Отладка
.collect(Collectors.toList()); // Терминальная операция
// Вывод:
// Processing: 1
// Processing: 2
// After filter: 2
// After map: 4
// Processing: 3
// Processing: 4
// After filter: 4
// After map: 8
// Processing: 5
3. Важное: Lazy Evaluation
peek() БЕЗ терминальной операции
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// НИЧЕГО НЕ ПРОИЗОЙДЁТ!
Stream<Integer> stream = numbers.stream()
.peek(n -> System.out.println("Processing: " + n))
.filter(n -> n > 2);
// peek() НЕ выполнится, потому что нет терминальной операции
peek() С терминальной операцией
// СЕЙЧАС выполнится peek()
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("Processing: " + n))
.filter(n -> n > 2)
.collect(Collectors.toList()); // Терминальная операция!
// Вывод:
// Processing: 1
// Processing: 2
// Processing: 3
// Processing: 4
// Processing: 5
4. Практические примеры
Пример 1: Отладка pipeline'а
public class StreamDebug {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana", "apricot", "blueberry");
List<String> result = words.stream()
.peek(w -> System.out.println("1. Original: " + w))
.filter(w -> w.startsWith("a"))
.peek(w -> System.out.println("2. After filter: " + w))
.map(String::toUpperCase)
.peek(w -> System.out.println("3. After map: " + w))
.collect(Collectors.toList());
// Вывод:
// 1. Original: apple
// 2. After filter: apple
// 3. After map: APPLE
// 1. Original: banana
// 1. Original: apricot
// 2. After filter: apricot
// 3. After map: APRICOT
// 1. Original: blueberry
}
}
Пример 2: Логирование
public class Logger {
private static final java.util.logging.Logger logger =
java.util.logging.Logger.getLogger(Logger.class.getName());
public List<Integer> processNumbers(List<Integer> numbers) {
return numbers.stream()
.peek(n -> logger.info("Processing number: " + n))
.filter(n -> n > 0)
.peek(n -> logger.info("After filter: " + n))
.map(n -> n * n)
.peek(n -> logger.info("After square: " + n))
.collect(Collectors.toList());
}
}
Пример 3: Мониторинг производительности
public class PerformanceMonitor {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
long startTime = System.currentTimeMillis();
List<Integer> result = numbers.stream()
.peek(n -> {
long elapsed = System.currentTimeMillis() - startTime;
System.out.println("Processing " + n + " at " + elapsed + "ms");
})
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Result: " + result);
}
}
Пример 4: Сборка статистики
public class Statistics {
static class Counter {
int count = 0;
long sum = 0;
}
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Counter counter = new Counter();
List<Integer> result = numbers.stream()
.filter(n -> n > 2)
.peek(n -> {
counter.count++;
counter.sum += n;
})
.collect(Collectors.toList());
System.out.println("Count: " + counter.count);
System.out.println("Sum: " + counter.sum);
System.out.println("Average: " + (counter.sum / counter.count));
}
}
5. peek() vs forEach()
forEach() — терминальная операция
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// forEach() ЗАВЕРШАЕТ pipeline, ничего не возвращает
numbers.stream()
.filter(n -> n > 2)
.forEach(n -> System.out.println(n));
// Дальше нельзя применять операции
// .map(...) — ОШИБКА: forEach() уже завершил поток
peek() — промежуточная операция
// peek() НЕ завершает pipeline, возвращает Stream
List<Integer> result = numbers.stream()
.filter(n -> n > 2)
.peek(n -> System.out.println(n)) // Можно продолжать
.map(n -> n * 2) // Добавляем ещё операции
.collect(Collectors.toList()); // Теперь завершаем
| Операция | Тип | Что возвращает | Использование |
|---|---|---|---|
| peek() | Промежуточная | Stream | Отладка, логирование, мониторинг |
| forEach() | Терминальная | void | Выполнение действий, завершение pipeline |
6. Когда использовать peek()
Правильное использование
// 1. Отладка
Stream.of(1, 2, 3)
.peek(System.out::println) // Посмотреть промежуточные значения
.filter(n -> n > 1)
.collect(Collectors.toList());
// 2. Логирование
List<User> users = userService.getAll();
List<User> filtered = users.stream()
.peek(u -> logger.debug("Processing user: " + u.getId()))
.filter(User::isActive)
.collect(Collectors.toList());
Неправильное использование
// ПЛОХО: использование для побочных эффектов
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
.peek(n -> database.save(n)) // ОПАСНО: побочный эффект
.filter(n -> n > 1)
.collect(Collectors.toList());
// ХОРОШО: использование forEach() для действий
numbers.stream()
.filter(n -> n > 1)
.forEach(n -> database.save(n));
7. Специализированные версии
// IntStream
IntStream.range(1, 5)
.peek(n -> System.out.println("Processing: " + n))
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
// LongStream
LongStream.range(1, 5)
.peek(n -> System.out.println("Processing: " + n))
.boxed()
.collect(Collectors.toList());
// DoubleStream
DoubleStream.of(1.5, 2.5, 3.5)
.peek(System.out::println)
.filter(d -> d > 2)
.forEach(System.out::println);
8. Оптимизация: Когда peek() НЕ выполнится
// ОПАСНО: если после peek() есть short-circuit операция
boolean result = Stream.of(1, 2, 3, 4, 5)
.peek(n -> System.out.println("Checking: " + n))
.anyMatch(n -> n == 3); // Остановится на 3-м элементе
// Вывод:
// Checking: 1
// Checking: 2
// Checking: 3
// (дальше не выполнится)
9. Лучшие практики
public List<User> getActiveUsers() {
return userRepository.findAll()
.stream()
.peek(u -> logger.debug("Processing user: {}", u.getId()))
.filter(User::isActive)
.peek(u -> metrics.incrementActiveUserCount())
.sorted(Comparator.comparing(User::getCreatedAt))
.peek(u -> logger.debug("Sorted user: {}", u.getName()))
.collect(Collectors.toList());
}
10. Альтернативы
// Способ 1: peek() для отладки
Stream.of(1, 2, 3).peek(System.out::println).collect(...);
// Способ 2: forEach() если хочется побочные эффекты
Stream.of(1, 2, 3).forEach(System.out::println);
// Способ 3: map() если нужна трансформация
Stream.of(1, 2, 3)
.map(n -> {
System.out.println(n);
return n;
})
.collect(...);
// Способ 4: tapEach() в RxJava
Flux.fromIterable(list)
.doOnNext(n -> System.out.println(n))
.filter(...)
.subscribe();
Заключение
peek() — это очень полезный инструмент для отладки и логирования в Stream API. Важно помнить:
- peek() — промежуточная операция, не требует терминальной
- peek() выполняется только с терминальной операцией (lazy evaluation)
- Используйте peek() для отладки и логирования, не для побочных эффектов
- Не забывайте про short-circuit операции, которые могут прервать выполнение
- forEach() — для действий, peek() — для наблюдения