3.5 利用peek方法对流进行调试

问题

用户希望在处理流时查看流中的各个元素。

方案

根据需要,在流的流水线中调用 java.util.stream.Stream 接口定义的中间操作 peek

讨论

流处理由一系列零个或多个中间操作构成,后跟终止操作。每个中间操作都返回一个新的流,而终止操作将返回不是流的值。

对于流的流水线所涉及的中间操作序列,初次接触 Java 8 的用户有时候会感到困惑,因为无法在处理流时查看各个元素的值。

我们考虑这样一个简单的方法,即接受某个整数流的开始范围和结束范围,并将每个数字倍增,然后仅对能被 3 整除的结果值求和,如例 3-32 所示。

例 3-32 对整数进行倍增、筛选与求和

  1. public int sumDoublesDivisibleBy3(int start, int end) {
  2. return IntStream.rangeClosed(start, end)
  3. .map(n -> n * 2)
  4. .filter(n -> n % 3 == 0)
  5. .sum();
  6. }

编写一个简单的测试,验证程序可以正确执行:

  1. @Test
  2. public void sumDoublesDivisibleBy3() throws Exception {
  3. assertEquals(1554, demo.sumDoublesDivisibleBy3(100, 120));
  4. }

上述测试虽然有一定帮助,但并未提供太多有价值的信息。如果代码无法运行,很难找出症结所在。

如例 3-33 所示,我们为流水线添加一个 map 操作,传入并打印每个值,然后再次返回这些值。

例 3-33 添加标识映射以便打印

  1. public int sumDoublesDivisibleBy3(int start, int end) {
  2. return IntStream.rangeClosed(start, end)
  3. .map(n -> {
  4. System.out.println(n);
  5. return n;
  6. })
  7. .map(n -> n * 2)
  8. .filter(n -> n % 3 == 0)
  9. .sum();
  10. }

➊ 标识映射(identity map)打印并返回每个元素

程序将打印从 start(含)到 end(含)的数字,每行一个数字。尽管不应在生产环境中使用上述代码,但它的确能让用户在不影响流处理的同时观察其内部操作。

这正是 Stream 接口中 peek 方法的工作原理,该方法的声明如下:

  1. Stream<T> peek(Consumer<? super T> action)

根据 Javadoc 的描述,peek 方法“返回一个由流的元素构成的流,当元素从所生成的流中消耗时,对每个元素执行给定的操作”。由于 Consumer 仅传入一个输入而不返回任何值,所提供的 Consumer 不会对值造成破坏。

peek 方法是一种中间操作,可以根据需要多次添加,如例 3-34 所示。

例 3-34 使用多个 peek 方法

  1. public int sumDoublesDivisibleBy3(int start, int end) {
  2. return IntStream.rangeClosed(start, end)
  3. .peek(n -> System.out.printf("original: %d%n", n))
  4. .map(n -> n * 2)
  5. .peek(n -> System.out.printf("doubled : %d%n", n))
  6. .filter(n -> n % 3 == 0)
  7. .peek(n -> System.out.printf("filtered: %d%n", n))
  8. .sum();
  9. }

❶ 在倍增前打印值

❷ 在倍增后、筛选前打印值

❸ 在筛选后、求和前打印值

程序将显示每个元素的初始值、倍增值以及筛选后的值,输出结果如下:

  1. original: 100
  2. doubled : 200
  3. original: 101
  4. doubled : 202
  5. original: 102
  6. doubled : 204
  7. filtered: 204
  8. ...
  9. original: 119
  10. doubled : 238
  11. original: 120
  12. doubled : 240
  13. filtered: 240

略显不便的是,很难将用于测试的 peek 方法设置为可选。因此,尽管 peek 方法有助于调试,但不应将其置于生产环境中。