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 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 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 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 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下载。