在前面的博客文章(“ 延迟执行Java的供应商 “),我引用礁HORSTMANN的陈述书中‘ 的Java SE8为真的很急关于lambda表达式’,‘所有的lambda表达式的点被推迟执行 。’ Horstmann在最后一年为Dobb博士的杂志写了一篇名为“ Java 8中的Lambda表达式 ”的文章,其中他使用不同的术语写了类似的声明,“ Lambda表达式是可以传递的代码块,因此可以之后执行一次,一次或多次。”
在该较早的文章中 ,我研究了JDK中的lambda表达式如何与标准功能接口Supplier一起使用,以在“仅在必要时提供”单个值且未传递任何参数的情况下支持延迟执行。 在本文中,我重点介绍JDK提供的示例,这些示例使用Consumer标准功能接口“仅在必要时”“使用”或“处理”特定代码块。 Supplier
接受任何参数并仅返回一个响应,而Consumer
接受一个或多个参数并且不返回响应。 在Supplier
上调用的方法是get()
方法,并且是Consumer
的accept(T)
方法。 根据定义, Consumer
预计将有“副作用”,因为它“消耗”所提供的代码块。
java.util.function软件包中提供了许多Consumer
风格的标准功能接口。 这些都不返回结果(这就是为什么他们是消费者!),但是它们接受的参数的数量和类型不同(但是它们都接受至少一个参数)。 这些在这里列出:
- 消费者 –接受单个论点的一般
Consumer
,将成为本文大部分示例的关注中心。 - BiConsumer –接受两个参数,而不是一个参数(“ 消费者的两类专业化”)
- DoubleConsumer –适用于原始
double
的特殊消费者 - IntConsumer –原始
int
的专门消费者 - LongConsumer –适用于原始
long
的专业消费者 - ObjDoubleConsumer –接受两个参数的专用消费者 ,第一个为
Object
类型,第二个为double
类型 - ObjIntConsumer –接受两个参数的专用消费者 ,第一个参数为
Object
类型,第二个参数为int
类型 - ObjLongConsumer –接受两个参数的专用消费者,第一个参数为
Object
类型,第二个参数为long
类型
本文的其余部分将研究Consumer
和相关类的JDK使用的子集,以帮助演示它们如何以及何时有用。
偷看流元素流
在博客文章“ 使用Stream.peek窥视Java Streams内部 ”中,我讨论了可用于查看流中流动元素的中间操作 Stream.peek(Consumer) 。 这对于了解各种流操作对其各自的流元素所做的操作非常有用。 一种常见的实现方法是让提供给peek
方法的Consumer
是对System.out的调用。 println将当前处理的流元素打印到标准输出(或记录该元素或将其打印到标准错误)。 Javadoc文档中为Stream.peek(Consumer)方法提供了一个示例:
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(-)
方法的各种重载版本都接受参数,但不返回任何内容,因此它们完全符合“ Consumer”的概念。
在迭代流元素上指定操作
尽管Stream.peek(Consumer)
是一个中间操作,但Stream提供了另外两个接受Consumer
方法,它们都是终端操作 ,并且都是“针对每个”方法。 方法Stream.forEach(Consumer)是一种对流的元素以“显式不确定性”的方式执行由提供的Consumer
指定的操作的方法。 如果该流具有遇到顺序,则Stream.forEachOrdered(Consumer)方法将以所提供的Consumer
以流的“ 遇到顺序 ”执行指定的操作。 在这两种方法的情况下,基于Consumer
的“动作”应为“ 不干扰” 。 两种方法都在下面演示。
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)
方法来迭代流。 这些示例还演示了如何通过首先在这些集合上调用stream()
对Set
和List
进行此操作。 有方便的方法,但是,通过限定可迭代和执行由这些集合的实现,其接受一个Consumer
,并允许使用该集合的迭代forEach
方法。 下一个代码清单中显示了此示例。
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接口没有像Set
和List
那样扩展Iterable
接口,但是Java Map
仍然具有类似的功能,可以指定使用者“消费” Map
每个条目。 因为Map
有两个输入参数(键和值),所以它的forEach
方法接受BiConsumer而不是到目前为止本文中讨论的Consumer 。 接下来显示一个简单的示例。
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
方法,并在下一个代码清单中进行了演示。
StackWalker.getInstance().forEach(out::println);
尽管JDK对Consumer , BiConsumer以及其他类型的标准Consumer样式功能接口有更多的JDK使用 ,但本文中我要介绍的最后一个示例来自Optional类。
仅在存在时应用
方法Optional.ifPresent(Consumer)和Optional.ifPresentOrElse(Consumer)推迟执行提供的Consumer
,以便仅在Optional
不是“空”(包含非null
值)的情况下才调用提供的Consumer
。 这是一个简单但功能强大的概念,而简单且人为的示例说明了它们是如何工作的。
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
不为空,则Optional.ifPresent
和JDK 9引入的Optional.ifPresentOrElse()
仅调用提供的Consumer
。 如果Optional
为空,则ifPresent
方法不执行任何操作,而ifPresentOrElse
调用第二个参数( Runnable )。
接受一个或多个参数且不返回任何结果的标准Java功能接口包括一般的Consumer
以及某些专门的使用者。 这些对于将执行推迟到给定条件发生之前(例如迭代或确定存在)有用,并且在该条件发生时要应用的行为涉及一个或多个输入自变量,而无需提供响应。 GitHub上提供了本文中显示的源代码示例。
翻译自: https://www.javacodegeeks.com/2018/06/deferred-execution-java-consumer.html