В одном из предыдущих постов в блоге (« Отложенное выполнение с поставщиком 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 — Специализированный потребитель для примитивных
ints - 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, являются их собственными. |