1.介绍
Java Stream API 向我们介绍了处理数据的强大替代方案。
在这个文章中将重点介绍 peek(),这是一种经常被误解的方法。
2.快速开始
有一个名字流,想把它们打印到控制台。
由于 peek() 期望 Consumer 作为其唯一参数,它看起来很合适,所以试一试:
@Test
public void test1(){
Stream<String> nameStream = Stream.of("张三", "李四", "王五");
nameStream.peek(System.out::println);
}
但是,上面的代码片段不产生任何输出。 要了解原因,需要回顾一下流生命周期的各个方面。
3.中间与终端操作
回想一下,流由三部分组成:数据源、零个或多个中间操作以及零个或一个终端操作。
源向管道提供元素。
中间操作一一获取元素并对其进行处理。 所有中间操作都是惰性的,因此,在管道开始工作之前,任何操作都不会产生任何影响。
终端操作意味着流生命周期的结束。 对于我们的场景最重要的是,它们启动了管道中的工作。
4.peek() 用法
peek() 在我们的第一个示例中不起作用的原因是它是一个中间操作,没有对管道应用终端操作。 或者,可以使用具有相同参数的 forEach() 来获得所需的行为:
@Test
public void test1(){
Stream<String> nameStream = Stream.of("张三", "李四", "王五");
nameStream.peek(System.out::println);
}
peek() 的 Javadoc 页面说:“这个方法的存在主要是为了支持调试,如果希望在元素流过管道中的某个点时看到它们的值”。
例如:
@Test
public void test3(){
List<String> collect = Stream.of("one", "two", "three", "four")
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("过滤值: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("映射值: " + e))
.collect(Collectors.toList());
System.out.println(collect);
}
输出
过滤值: three
映射值: THREE
过滤值: four
映射值: FOUR
[THREE, FOUR]
它演示了如何观察通过每个操作的元素。
最重要的是, peek() 在另一种情况下很有用:想要改变元素的内部状态时。 例如,假设想在打印之前将所有用户名转换为小写
@Data
@AllArgsConstructor
public class UserPeek {
private String name;
}
@Test
public void test4(){
Stream<UserPeek> userStream = Stream.of(new UserPeek("Alice"), new UserPeek("Bob"), new UserPeek("Chuck"));
userStream.peek(u -> u.setName(u.getName().toLowerCase()))
.forEach(System.out::println);
}
输出
UserPeek(name=alice)
UserPeek(name=bob)
UserPeek(name=chuck)
或者,可以使用 map(),但 peek() 更方便,因为不想替换元素。