В одном из предыдущих постов в блоге (« Отложенное выполнение с поставщиком Java ») я ссылался на высказывание Кея Хорстманна в книге « Java SE8 для действительно нетерпеливых », касающееся лямбда-выражений: «Точка всех лямбд — это отложенное выполнение ». Хорстманн написал статью под названием « Лямбда-выражения в Java 8 » для журнала доктора Добба в последний год, в котором он написал аналогичное утверждение, используя другую терминологию: «Лямбда-выражение — это блок кода, который вы можете передать, чтобы его можно было выполняется позже, один раз или несколько раз ».
В той предыдущей статье я рассмотрел, как стандартный функциональный интерфейс Supplier используется с лямбда-выражениями в JDK для поддержки отложенного выполнения для случаев, когда одно значение «предоставляется только при необходимости» и без каких-либо аргументов, передаваемых ему. В этой статье я остановлюсь на предоставленных JDK примерах использования стандартного функционального интерфейса Consumer для «потребления» или «обработки» определенного блока кода «только при необходимости». В то время как Supplier
принимает аргументов и возвращает ровно один ответ, Consumer
принимает один или несколько аргументов и не возвращает ответа. Метод, вызванный для Supplier
является методом get()
метод accept(T)
для Consumer
. По определению ожидается, что Consumer
будет иметь «побочные эффекты», поскольку он «потребляет» предоставленный кодовый блок.
В пакете java.util.function имеется множество стандартных функциональных интерфейсов в стиле Consumer
. Ни один из них не возвращает результат (именно поэтому они являются потребителями!), Но они отличаются по количеству и типам аргументов, которые они принимают (но все они принимают по крайней мере один аргумент). Они перечислены здесь:
- Потребитель — Генеральный
Consumer
который принимает один аргумент и будет в центре внимания большинства примеров этого поста. - BiConsumer — принимает два аргумента вместо одного («двухартеальная специализация потребителя »)
- DoubleConsumer — Специализированный потребитель, предназначенный для примитивных
double
с - IntConsumer — Специализированный потребитель для примитивных
int
s - LongConsumer — специализированный потребитель, предназначенный для примитивных
- ObjDoubleConsumer — Специализированный потребитель, который принимает два аргумента, первый из которых имеет тип
Object
а второй —double
тип - ObjIntConsumer — специализированный потребитель, который принимает два аргумента, первый из которых имеет тип
Object
а второй — типint
- ObjLongConsumer — Специализированный потребитель, который принимает два аргумента, первый из которых имеет тип
Object
а второй —long
В оставшейся части этого поста мы рассмотрим подмножество использования JDK Consumer
и связанных классов, чтобы продемонстрировать, как и когда они полезны.
Смотрит на поток элементов потока
В блоге « Просмотр внутренних потоков Java с помощью Stream.peek » я рассмотрел промежуточную операцию Stream.peek (Consumer), которую можно использовать для просмотра потоковых элементов потока. Это может быть очень полезно для понимания того, что различные операции потока делают с их соответствующими элементами потока. Распространенным способом сделать это является предоставление Consumer
методу peek
вызова System.out . println, который печатает текущий обработанный элемент потока в стандартный вывод (или регистрирует элемент или печатает его со стандартной ошибкой). Пример этого приведен в документации Javadoc для метода Stream.peek (Consumer):
1
2
3
4
5
6
|
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()); |
Поскольку различные перегруженные версии метода println(-)
принимают параметр, но ничего не возвращают, они идеально соответствуют концепции «Потребитель».
Указание действия для элементов итерированного потока
В то время как Stream.peek(Consumer)
является промежуточной операцией, Stream предоставляет два других метода, которые принимают Consumer
которые являются обеими терминальными операциями и являются «для каждого» метода. Метод Stream.forEach (Consumer) — это метод, который выполняет действие, указанное предоставленным Consumer
«явно недетерминированным» способом для элементов потока. Метод Stream.forEachOrdered (Consumer) выполняет действие, указанное предоставленным Consumer
в « порядке встречи » потока, если этот поток имеет порядок встречи. В случаях обоих методов «действие», основанное на Consumer
должно быть « не мешающим ». Оба метода продемонстрированы ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
Set.of( "one" , "two" , "three" , "four" ) .stream() .forEach(i -> out.println(i.toUpperCase())); Stream.of( "one" , "two" , "three" , "four" ) .forEach(i -> out.println(i.toUpperCase())); List.of( "one" , "two" , "three" , "four" ) .stream() .forEachOrdered(i -> out.println(i.toUpperCase())); Stream.of( "one" , "two" , "three" , "four" ) .forEachOrdered(i -> out.println(i.toUpperCase())); |
Приведенные выше примеры выглядят и очень похожи. Наиболее очевидная ситуация, в которой forEach
может привести к резким отличиям от forEachOrdered
— это использование параллельной обработки потока. В этом случае большинство отправляет использовать forEach
вместо forEachOrdered
.
Указание действия над повторяемыми элементами
В предыдущих примерах кода было показано использование Stream.forEach(Consumer)
для итерации потока. Примеры также продемонстрировали, как сделать это для Set
и List
, сначала вызвав stream()
для этих коллекций. Однако существуют удобные методы, которые определяются Iterable и реализуются этими реализациями коллекций, которые принимают Consumer
и допускают итерацию этой коллекции с помощью метода forEach
. Примеры этого показаны в следующем листинге кода.
1
2
3
4
|
Set.of( "one" , "two" , "three" , "four" ) .forEach(i -> out.println(i.toUpperCase())); List.of( "one" , "two" , "three" , "four" ) .forEach(i -> out.println(i.toUpperCase())); |
Хотя я и использовал коллекции в моем примере выше, все, что реализует Iterable , обычно будет поддерживать метод forEach
(или нарушать объявленный контракт интерфейса).
Указание действия при итерации записей карты
Хотя интерфейс Java в Map не расширяет интерфейс Iterable
как это делают Set
и List
, в Java Map
все еще была предусмотрена аналогичная возможность указать потребителю «потреблять» каждую запись в Map
. Поскольку у Map
есть два входных аргумента (ключ и значение), ее метод forEach
принимает BiConsumer вместо Consumer, который обсуждался в этом посте. Простой пример показан ниже.
1
2
3
4
5
|
Map.of( "Denver" , "Colorado" , "Cheyenne" , "Wyoming" , "Salt Lake City" , "Utah" , "Boise" , "Idaho" ) .forEach((c, s) -> out.println(c + " is the capital of " + s)); |
Хождение по стеку
StackWalker — это долгожданное дополнение к JDK 9, которое обеспечивает поточно- ориентированный подход к просмотру трассировки стека и является значительным улучшением по сравнению с подходом StackTraceElement . Возможно, разработчики чаще используют StackWalker.walk (Function) , но этот пост посвящен Consumer
поэтому основное внимание уделяется StackWalker.forEach (Consumer) . Этот метод аналогичен ранее рассмотренным Stream.forEach
и Iterable.forEach
и продемонстрирован в следующем листинге кода.
1
|
StackWalker.getInstance().forEach(out::println); |
Хотя в JDK используется гораздо больше Consumer , BiConsumer и других типов стандартных функциональных интерфейсов в стиле Consumer, последние примеры, о которых я расскажу в этом посте, относятся к классу Optional .
Применяется только при наличии
Методы Optional.ifPresent (Consumer) и Optional.ifPresentOrElse (Consumer) откладывают выполнение предоставленных Consumer
, так что предоставленный Consumer
будет вызываться, только если Optional
не «пуст» (содержит null
значение). Это простая, но мощная концепция, и упрощенные и надуманные примеры показывают, как они работают.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
public void demonstrateOptionalIfPresent() { getMiddleName( true ).ifPresent(n -> out.println( "Middle Name: " + n)); } public void demonstrateOptionalIfPresentOrElse() { getMiddleName( false ).ifPresentOrElse( n -> out.println( "Middle Name: " + n), () -> displayMissingMiddleName()); } private Optional<String> getMiddleName( final boolean present) { return present ? Optional.of( "Wayne" ) : Optional.empty(); } private void displayMissingMiddleName() { out.println( "No middle name provided!" ); } |
Как показано в приведенном выше листинге кода, как Optional.ifPresent
и Optional.ifPresentOrElse()
представленный в JDK 9, вызывают предоставленного Consumer
если Optional
не пуст. Если Optional
пуст, метод ifPresent
ничего не делает, а ifPresentOrElse
вызывает второй аргумент ( Runnable ).
Стандартные функциональные интерфейсы Java, которые принимают один или несколько аргументов и не возвращают результата, включают обычного Consumer
а также некоторых специализированных потребителей. Они полезны для отсрочки выполнения до тех пор, пока не произойдет заданное условие (например, при повторении или при определении наличия), и поведение, которое нужно применить при возникновении этого условия, включает в себя один или несколько входных аргументов и нет необходимости предоставлять ответ. Примеры исходного кода, показанные в этом посте, доступны на GitHub .
Опубликовано на Java Code Geeks с разрешения Дастина Маркса, партнера нашей программы JCG . См. Оригинальную статью здесь: отложенное выполнение с потребителем Java
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |