基础知识

Java 8 Streams peek API

2019-01-28  本文已影响36人  大哥你先走

1. 简介

Stream API 为Java 处理数据提供了一种强大的替代方法。在这篇文章中,我们主要介绍Stream API 中一个经常被错误理解的方法peek()

2. 样例

假设我们有一个Person的流,我们想将Person的名字,年龄等信息打印到控制台。peek()方法的唯一参数是Consumer<? super T>看上去这就是我们想要的(不是唯一可以满足我们的):

Person 定义:

public class Person {
 private String name;
 private int age;
}

样例 1:

 @Test
 public void test_printInfoButNot() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.peek(System.out::println);
 }

非常的遗憾,上面的代码没有任何输出。为了理解上面的代码为什么没有输出,我们快速的回顾一下与Stream生命周期相关的知识。

3. 中间操作 vs. 终止操作

流由三部分构成:数据源,一个或多个中间操作,一个或多个终止操作。

数据源:为流提供数据。

中间操作:依次获取数据并处理数据。所有的中间操作都是“懒操作”,也就是说在流开始工作之前,中间操作对流中的数据没有任何影响。

终止操作:流生命周期的结束,它可以触发流开始工作,中间操作也就会影响流中的数据。

4. 使用 peek()

在我们的例子中,peek()不起作用的原因是peek()是一个中间操作,而且我们没有提供一个终止操作。我们可以使用forEach(和peek()参数一样)来实现打印用户名和年龄的目标:

样例 2:

 @Test
 public void test_printInfo() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.forEach(System.out::println);
 }

样例 2 输出结果如下:

 Person{name='a', age=18}
 Person{name='b', age=23}
 Person{name='c', age=34}

按照Java团队的说法,peek()方法存在的主要目的是用调试,通过peek()方法可以看到流中的数据经过每个处理点时的状态。一个简单的样例如下:

样例 3:

 @Test
 public void test_peekDebug() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.filter(person -> person.getAge() < 30)
 .peek(person -> System.out.println("filter " + person))
 .map(person -> new Person(person.getName() + " map", person.getAge()))
 .peek(person -> System.out.println("map " + person))
 .collect(Collectors.toList());
 }

样例 3 输出结果如下:

 filter Person{name='a', age=18}
 map Person{name='a map', age=18}
 filter Person{name='b', age=23}
 map Person{name='b map', age=23}

通过输出结果来看,peek()方法确实能够帮助我们观察传递给每个操作的数据。

除去用于调试,peek()在需要修改元素内部状态的场景也非常有用,比如我们想将所有Person的名字修改为大写,当然也可以使用map()flatMap实现,但是相比来说peek()更加方便,因为我们并不想替代流中的数据。

样例 4 :

 @Test
 public void test_modifyInnerState() {
 Person a = new Person("a", 18);
 Person b = new Person("b", 23);
 Person c = new Person("c", 34);
 Stream<Person> persons = Stream.of(a, b, c);
 persons.peek(person -> person.setName(person.getName().toUpperCase()))
 .forEach(System.out::println);
 }

样例 4 的输出结果如下:

 Person{name='A', age=18}
 Person{name='B', age=23}
 Person{name='C', age=34}

5. 总结

在这篇文章中我们简单回顾了流的生命周期,组成部分以及peek()工作的原理。还介绍了peek()在调试和修改数据状态方面的应用。文中所有样例的代码可以从GitHub下载。

上一篇 下一篇

猜你喜欢

热点阅读