还在stream中使用peek?不要被这些陷阱绊住了
简介
自从JDK中引入了stream之后,仿佛一切都变得很简单,根据stream提供的各种方法,如map,peek,flatmap等等,让我们的编程变得更美好。
事实上,我也经常在项目中看到有些小伙伴会经常使用peek来进行一些业务逻辑处理。
那么既然JDK文档中说peek方法主要是在调试的情况下使用,那么peek一定存在着某些不为人知的缺点。一起来看看吧。
peek的定义和基本使用
先来看看peek的定义:
Stream<T> peek(Consumer<? super T> action);
peek方法接受一个Consumer参数,返回一个Stream结果。
而Consumer是一个FunctionalInterface,它需要实现的方法是下面这个:
void accept(T t);
accept对传入的参数T进行处理,但是并不返回任何结果。
我们先来看下peek的基本使用:
public static void peekOne(){
Stream.of(1, 2, 3)
.peek(e -> log.info(String.valueOf(e)))
.toList();
}
运行上面的代码,我们可以得到:
[main] INFO com.flydean.Main - 1
[main] INFO com.flydean.Main - 2
[main] INFO com.flydean.Main - 3
逻辑很简单,就是打印出Stream中的元素而已。
peek的流式处理
peek作为stream的一个方法,当然是流式处理的。接下来我们用一个具体的例子来说明流式处理具体是如何操作的。
public static void peekForEach(){
Stream.of(1, 2, 3)
.peek(e -> log.info(String.valueOf(e)))
.forEach(e->log.info("forEach"+e));
}
这一次我们把toList方法替换成了forEach,通过具体的打印日志来看看到底发生了什么。
[main] INFO com.flydean.Main - 1
[main] INFO com.flydean.Main - forEach1
[main] INFO com.flydean.Main - 2
[main] INFO com.flydean.Main - forEach2
[main] INFO com.flydean.Main - 3
[main] INFO com.flydean.Main - forEach3
通过日志,我们可以看出,流式处理的流程是对应流中的每一个元素,分别经历了peek和forEach操作。而不是先把所有的元素都peek过后再进行forEach。
Stream的懒执行策略
之所有会有流式操作,就是因为可能要处理的数据比较多,无法一次性加载到内存中。
所以为了优化stream的链式调用的效率,stream提供了一个懒加载的策略。
什么是懒加载呢?
就是说stream的方法中,除了部分terminal operation之外,其他的都是intermediate operation.
比如count,toList这些就是terminal operation。当接受到这些方法的时候,整个stream链条就要执行了。
而peek和map这些操作就是intermediate operation。
intermediate operation的特点是立即返回,如果最后没有以terminal operation结束,intermediate operation实际上是不会执行的。
我们来看个具体的例子:
public static void peekLazy(){
Stream.of(1, 2, 3)
.peek(e -> log.info(String.valueOf(e)));
}
运行之后你会发现,什么输出都没有。
这表示peek中的逻辑并没有被调用,所以这种情况大家一定要注意。
peek为什么只被推荐在debug中使用
如果你阅读过peek的文档,你可能会发现peek是只被推荐在debug中使用的,为什么呢?
JDK中的原话是这样说的:
In cases where the stream implementation is able to optimize away the production of some or all the elements (such as with short-circuiting operations like findFirst, or in the example described in count), the action will not be invoked for those elements.
翻译过来的意思就是,因为stream的不同实现对实现方式进行了优化,所以不能够保证peek中的逻辑一定会被调用。
我们再来举个例子:
public static void peekNotExecute(){
Stream.of(1, 2, 3)
.peek(e -> log.info("peekNotExecute"+e))
.count();
}
这里的terminal operation是count,表示对stream中的元素进行统计。
因为peek方法中参数是一个Consumer,它不会对stream中元素的个数产生影响,所以最后的运行结果就是3。
peek中的日志输出并没有打印出来,表示peek没有被执行。
所以,我们在使用peek的时候,一定要注意peek方法是否会被优化。要不然就会成为一个隐藏很深的bug。
peek和map的区别
好了,讲到这里,大家应该对peek有了一个全面的认识了。但是stream中还有一个和peek类似的方法叫做map。他们有什么区别呢?
前面我们讲到了peek方法需要的参数是Consumer,而map方法需要的参数是一个Function:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Function也是一个FunctionalInterface,这个接口需要实现下面的方法:
R apply(T t);
可以看出apply方法实际上是有返回值的,这跟Consumer是不同的。所以一般来说map是用来修改stream中具体元素的。 而peek则没有这个功能。
peek方法接收一个Consumer的入参. 了解λ表达式的应该明白 Consumer的实现类应该只有一个方法,该方法返回类型为void. 它只是对Stream中的元素进行某些操作,但是操作之后的数据并不返回到Stream中,所以Stream中的元素还是原来的元素.
map方法接收一个Function作为入参. Function是有返回值的, 这就表示map对Stream中的元素的操作结果都会返回到Stream中去.
- 要注意的是,peek对一个对象进行操作的时候,虽然对象不变,但是可以改变对象里面的值。
大家可以运行下面的例子:
public static void peekUnModified(){
Stream.of(1, 2, 3)
.peek(e -> e=e+1)
.forEach(e->log.info("peek unModified"+e));
}
public static void mapModified(){
Stream.of(1, 2, 3)
.map(e -> e=e+1)
.forEach(e->log.info("map modified"+e));
}
总结
以上就是对peek的总结啦,大家在使用的时候一定要注意存在的诸多陷阱。
本文的例子https://github.com/ddean2009/learn-java-base-9-to-20/tree/master/peek-and-map/
更多文章请看 www.flydean.com
还在stream中使用peek?不要被这些陷阱绊住了的更多相关文章
- Stream中的Peek操作
1.引言 如果你试图对流操作中的流水线进行调试, 了解stream流水线每个操作之前和操作之后的中间值, 该如何去做? 首先我们看一个例子, 使用forEach将流操作的结果打印出来. 1 /** 2 ...
- java 8 Stream中操作类型和peek的使用
目录 简介 中间操作和终止操作 peek 结论 java 8 Stream中操作类型和peek的使用 简介 java 8 stream作为流式操作有两种操作类型,中间操作和终止操作.这两种有什么区别呢 ...
- JAVA8之lambda表达式具体解释,及stream中的lambda使用
前言: 本人也是学习lambda不久,可能有些地方描写叙述有误,还请大家谅解及指正! lambda表达式具体解释 一.问题 1.什么是lambda表达式? 2.lambda表达式用来干什么的? 3.l ...
- TImage保存图片到Stream及从Stream中取图片
因为一个项目,不得不将图片保存到数据库中,需要的时候再从数据库中读取.初时,以为很简单,不就是一个Stream.事实上,也很简单.度娘一下,代码也很多,但,都是坑! 看一下TImage的源,Pictu ...
- 【Java必修课】图说Stream中的skip()和limit()方法及组合使用
1 简介 本文将讲解Java 8 Stream中的两个方法:skip()和limit().这两个方法是Stream很常用的,不仅各自会被高频使用,还可以组合出现,并能实现一些小功能,如subList和 ...
- java 8 stream中的Spliterator简介
目录 简介 tryAdvance trySplit estimateSize characteristics 举个例子 总结 java 8 stream中的Spliterator简介 简介 Split ...
- c++中的peek函数
c++中 cin.peek()函数 其返回值是一个char型的字符,返回值是指针指向的当前字符, 但是只是观测,指针任停留在当前位置,并不后移.如果要访问的字符是文件结束符,则函数值是EOF(-1); ...
- 十年测试老鸟告诉你--自动化测试选JAVA还是选Python--写给还在迷茫中的朋友
一.前言 Python和Java哪个更适合做自动化测试?这是很多测试工程师从功能跨入自动化纠结的问题,今天测试老鸟来带大家详细分析一下!写给还在迷茫中的朋友! 首先可以确认的是提出这个问题的肯定是一个 ...
- java stream中Collectors的用法
目录 简介 Collectors.toList() Collectors.toSet() Collectors.toCollection() Collectors.toMap() Collectors ...
- Stream中的Collector收集器原理
前言 Stream的基本操作因为平时工作中用得非常多(也能看到一些同事把Stream操作写得很丑陋),所以基本用法就不写文章记录了. 之所以能把Stream的操作写得很丑陋,完全是因为Stream底层 ...
随机推荐
- 「DIARY」PKUSC2021 小结
另外有一个纯吐槽游记版本的,还没有写完(快写完了,真的) 欢迎各路神仙来吐槽一个菜鸡的考场思路 # Day1 考场小结 总体而言,T1 完全就是送分,做得也挺快的:T2 大概是本场最难的题:然后 T3 ...
- ubuntu ssh远程访问出现Permission denied(publickey,password)解决方法
windows上安装SSH服务设置–>应用–>可选功能–>添加功能–>安装 OpenSSH服务器 和 OpenSSH客户端在左下角搜索栏输入服务,将相关SSH服务设置为自动(延 ...
- <四>JMeter数据库连接/后置处理器/断言简介
一.数据库连接 1.右键线程组添加--配置元件--JDB Cconnection Configuration 2.配置如下: URL为数据路连接地址,用户名密码为数据库用户名和密码 3.添加一个JDB ...
- WinCC插件制作教程
目录 插件的编写 插件的使用 参考资料 Creation of .NET Controls 109759944_Prepare.NetControls_DOC_en.pdf 插件的编写 创建插件项目, ...
- 第15章 授权:保护您的应用程序(ASP.NET Core in Action, 2nd Edition)
本章包括 使用授权控制谁可以使用你的应用 对策略使用基于声明的授权 创建自定义策略以处理复杂的需求 根据所访问的资源授权请求 隐藏用户未经授权访问的Razor模板中的元素 在第14章中,我向您展示了如 ...
- 记一次线上DB被打挂
这周刚新上了需求,在慢慢写代码的时候,突然报警群的消息多了,组长让我看看咋回事. 一开始没当回事,因为faas任务的错误日志一直很多,但是发现新的日志和以前大不相同,显示的是上游faas实例的连接被m ...
- redis 配置哨兵模式时出现的问题(redis 版本 6.2.5)
今天准备搭建一个 redis 集群(redis 版本 6.2.5),在这之前要先配置好哨兵模式. 但是在配置哨兵模式时出现了问题.之前没有搭建集群时(一主两从,三台虚拟机)可以顺利配置好,而搭建集群时 ...
- Java 方法详解 与数组
基础阶段: 1.何谓方法 何谓方法?◆System.out.println(),那么它是什么呢?◆Java方法是语句的集合,它们在- -起执行一个功能. ◆方法是解决一类问题的步骤的有序组合 ...
- python基础简记
pyhon 2 兼容 python 3 : 导入__future__包: 一些命名规定:以单划线开头的表示不能直接访问类属性,需要访问接口:以双划线开头的表示类的私有成员:以双划线开头和结尾的表示py ...
- IT工具知识-10:Markdown小技巧(不断更新)
Markdown小技巧 1. 如何实现在文内不同关键字间跳转 该技巧是基于typora软件下使用的,参考的这个教程:视频链接 该教程有两种跳转方式:一.使用Markdown语法,二.使用HTML的锚点 ...