Перейти к основному содержимому

Java 8 Streams peek() API

· 3 мин. чтения

1. Введение

Java Stream API представляет собой мощную альтернативу для обработки данных.

В этом коротком руководстве мы сосредоточимся на методе peek() , который часто неправильно понимают.

2. Быстрый пример

Давайте испачкаем руки и попробуем использовать peek() . У нас есть поток имен, и мы хотим вывести их на консоль.

Так как peek() ожидает Consumer<T> в качестве своего единственного аргумента, это кажется подходящим вариантом, так что давайте попробуем:

Stream<String> nameStream = Stream.of("Alice", "Bob", "Chuck");
nameStream.peek(System.out::println);

Однако приведенный выше фрагмент не производит вывода. Чтобы понять почему, давайте быстро вспомним аспекты жизненного цикла потока.

3. Промежуточные и терминальные операции

Напомним, что потоки состоят из трех частей: источника данных, нуля или более промежуточных операций и нуля или одной конечной операции.

Источник предоставляет элементы конвейеру.

Промежуточные операции получают элементы один за другим и обрабатывают их. Все промежуточные операции ленивы, и, как следствие, никакие операции не будут иметь никакого эффекта, пока конвейер не заработает.

Терминальные операции означают конец жизненного цикла потока. Что наиболее важно для нашего сценария, они инициируют работу в пайплайне .

4. Использование peek()

Причина , по которой функция peek() не работала в нашем первом примере, заключается в том, что это промежуточная операция, и мы не применяли терминальную операцию к конвейеру. В качестве альтернативы мы могли бы использовать forEach() с тем же аргументом, чтобы получить желаемое поведение:

Stream<String> nameStream = Stream.of("Alice", "Bob", "Chuck");
nameStream.forEach(System.out::println);

` [На странице Javadoc ](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/stream/Stream.html#peek(java.util.function.Consumer))peek()` говорится: « Этот метод существует в основном для поддержки отладки, когда вы хотите видеть, как элементы проходят через определенную точку в конвейере ».

Рассмотрим этот фрагмент с той же страницы Javadoc:

Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());

Он демонстрирует, как мы наблюдаем за элементами, прошедшими каждую операцию.

Кроме того, peek() может быть полезен в другом сценарии: когда мы хотим изменить внутреннее состояние элемента . Например, предположим, что мы хотим преобразовать все имена пользователей в нижний регистр перед их печатью:

Stream<User> userStream = Stream.of(new User("Alice"), new User("Bob"), new User("Chuck"));
userStream.peek(u -> u.setName(u.getName().toLowerCase()))
.forEach(System.out::println);

В качестве альтернативы мы могли бы использовать map() , но peek() более удобен, так как мы не хотим заменять элемент.

5. Вывод

В этом кратком руководстве мы рассмотрели краткий обзор жизненного цикла потока, чтобы понять, как работает функция peek() . Мы также видели два случая повседневного использования, когда использование peek() является наиболее простым вариантом.

И, как обычно, примеры доступны на GitHub .