← Назад к вопросам

Что делает 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. Важно помнить:

  1. peek() — промежуточная операция, не требует терминальной
  2. peek() выполняется только с терминальной операцией (lazy evaluation)
  3. Используйте peek() для отладки и логирования, не для побочных эффектов
  4. Не забывайте про short-circuit операции, которые могут прервать выполнение
  5. forEach() — для действий, peek() — для наблюдения
Что делает peek в Stream API? | PrepBro