上一次【http://www.cnblogs.com/webor2006/p/8297603.html】在最后用stream.iterate()生成了6个奇数,接着基于它来实现如下需求:找出该流中大于2的元素,然后再将每个元素乘以2,然后忽略掉流中的前两个元素,然后再取流中的前两个元素,最后求出流中元素的总和。那具体如何来实现呢?下面一个个条件来实现:

①、找出该流中大于2的元素。

很显然这是一个过滤操作,所以可以使用stream.fitler(),如下:

②、再将每个元素乘以2。

这个当然就是一种元素映射啦,所以可以使用stream.map(),如下:

而它接收的ToIntFunction的接口原型如下:

其具体代码如下:

那有个细节需要思考一下:

其实是为了避免自动装箱、自动拆箱, 为什么?对于map()这个方法要求的Function接口的返回结果必须是Integer类型的,而期望的最终结果应该是int类型的,所以用map()的话则就会自动装箱与自动拆箱,对于它是有一点点性能上的损耗的,可见Java8是极力想办法避勉这种细微的性能问题,因此这些特定类型的方法就应运而生,像mapToInt()中要求的ToIntFunction接口其返回结果就是直接的int类型,所以就不存在自动装箱与自动拆箱的问题,体会一下。

③、然后忽略掉流中的前两个元素。

怎么忽略呢?当然在流中已经提供了现成的方法啦,如下:

④、再取流中的前两个元素。

这时limit()就可以再次发挥作用,如下:

⑤、最后求出流中元素的总和。

直接可以使用stream.sum()方法,如下:

编译运行:

是不是32呢?咱们手动算一下:生成的6个奇数序列为:1, 3, 5, 7, 9, 11,接着从这6个中过滤出大于2的数为:3, 5, 7, 9, 11,然后再对它们进行乘2如下:6,10,14,18,22,然后忽略掉前两个数此时变为:14, 18, 22,再取出前两个元素如下:14, 18,最后求总和为:14 + 18 = 32,嗯~~没问题。

对于这么多的条件采用stream来实现是多么的简便,而且代码的可读性也比较好,试想一下如果采用传统的做法是不是大量的循环临时变量充斥其中,所以说Java8中Lambda表达式、函数式接口、Stream所带来的是突破性的改变。

接下来对于上面的需求再修改一下,将最后的元素求和改为最元素中最小的那个并打印出来 ,那如何搞呢?Stream中也提供有现成的方法,如下:

编译运行:

Optional咱们之前已经学习过了,对于OptionalInt那当然很好理解啦,里面的元素就是int类型,而非是一个泛型了,如下:

另外看一个方法定义的细节:

这是为什么呢?其实是返回int还是OptionalInt根本原因就是其取出来的值有没有可能为空,对于sum()方法而言,如果Stream中的元素为空,那最终直接显示0就好了,如下:

而如果换成min()或max(),这时会得到一个空的Stream,如下:

这时后如果在min()后面再去调用就会出问题了,如下:

所以因为min()返回的是OptionalInt类型,所以得用标准的用法,如下:

如果将过滤条件改成正常滴,如下:

目前stream()已经提供了求最小值、最大值、总和的方法,那如果想一次性将这三个需求全部求解出来,那怎么办呢?当然第一时间能想到的就是调用三次不同的方法不就可以解决了,但是这种方法显示是比较笨重的,其实Java8中还有更好的方法,如下:

其中看一下IntSummaryStatistics类的介绍:

其中针对咱们写的这个流操作的代码再来回顾一下它的分类:

接下来写一段如下代码,会有一些我们意想之外事情发生,如下:

上面由于调用的两个都是中间操作所以都会返回stream,那打印结果是啥呢?下面运行一下:

其重点看一下抛出的异常:流已经被操作了或者已经被关闭了,为什么?其实Java8中的流跟以前io中的流的概念其实是差不多的,意思是流如果被使用过了就不能再次使用了或者说如果流被关闭了也不能再次使用了,而对于咱们报错的这句代码显然流是木有被关闭的,如下:

那证明是该流已经被使用过了,再调用它的distinct()方法则就抛异常了,分析一下是不是这个原因:

那如何解决这个异常了,需对代码进行稍加修改,如下:

但是!!这里不是推荐的写法,推荐就是用链式的方式来写,而不要用临时的变量去接收再继续写,需要注意。

接下来再来探究另外一个问题就是关于stream中的中间操作与终止操作本质上的区别,举一个如下例子:将stream中的元素的首字母变成大写之后再将其输出,如下:

接下来改造一下代码,加入调试打印语句用来观察现象,如下:

编译运行:

如预期没有任何打印,此时如果再加上终止操作forEach(),

这也就是之前一直在说的关于stream包含两种操作:第一种是中间操作,第二种是终止操作,而对于流中所有的中间操作都是lazy的,也就是惰性求值的,如果没有遇到终止操作的时候它是不会执行的,只有当遇到了终止操作之后,而中间的若干个中间操作才会一并执行,这是需要谨记滴。

另上再回过头来看一下之前写的如下代码:

实际上不是咱们表面的想象的那样,要是性能低下Java8也不可能引进流的概念啦,实际上最终只会循环一次,为什么呢?可以这么简单的理解,以于这些中间操作的行为都是存放于Stream中的一个容器当中,一旦遇到终止操作时,则会将这些行为按照咱们写的顺序逐个的应用到集合当中的每一个元素上,所以说性能上不会有任何影响。

接下来继续看下面这个陷阱:

下面运行来论证一下我们的猜测:

这是为什么呢?

如何解决呢?当然就是先限定元素个数再来去重就成了,如下:

所以说对于stream里面的各种方法在实际编写代码时需要注意一下顺序,以勉掉到坑里面去。

java8学习之Stream陷阱剖析的更多相关文章

  1. java8学习之Stream实例剖析

    继续操练Stream,直接上代码: 而咱们要返回ArrayList,显示可以用构造引用来传递到里面,因为它刚好符合Supplier函数式接口的特性:不接收参数返回一个值,所以: 接下来试着将Strea ...

  2. java8学习之Stream分组与分区详解

    Stream应用: 继续举例来操练Stream,对于下面这两个集合: 需求是:将这两个集合组合起来,形成对各自人员打招呼的结果,输出的结果如: "Hi zhangsan".&quo ...

  3. java8学习之Stream介绍与操作方式详解

    关于默认方法[default method]的思考: 在上一次[http://www.cnblogs.com/webor2006/p/8259057.html]中对接口的默认方法进行了学习,那在Jav ...

  4. Java8学习(4)-Stream流

    Stream和Collection的区别是什么 流和集合的区别是什么? 粗略地说, 集合和流之间的差异就在于什么时候进行计算.集合是一个内存中的数据结构,它包含数据结构中目前所有的值--集合中的每个元 ...

  5. java8学习之Predicate深入剖析与函数式编程本质

    上次[http://www.cnblogs.com/webor2006/p/8214596.html]对Predicate函数接口进行了初步的学习,其中提到了在未来要学习的Stream中得到了大量的应 ...

  6. java8学习之Stream源码分析

    上一次已经将Collectors类中的各种系统收集器的源代码进行了完整的学习,而在之前咱们已经花了大量的篇幅对其Stream进行了详细的示例学习,如: 那接下来则通过源代码的角度来对Stream的运作 ...

  7. java8学习之Stream深度解析与源码实践

    继续对流进行学习,首先先说明一下流的特点: 1.Collection提供了新的stream()方法. 2.流不存储,通过管道的方式获取值. 3.本质是函数式的,对流的操作会生成一个结果,不过并不会修改 ...

  8. Java8学习笔记----Lambda表达式 (转)

    Java8学习笔记----Lambda表达式 天锦 2014-03-24 16:43:30 发表于:ATA之家       本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人 ...

  9. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

随机推荐

  1. SQL:SQL优化

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索 ...

  2. Redis客户端基本操作以及查看慢查询

    1.连接 redis-cli.exe -h 127.0.0.1 -p 6379 2.验证密码 λ redis-cli.exe -h 127.0.0.1 -p 6379127.0.0.1:6379> ...

  3. ubuntu安装成功之后需要做些什么?

    1.安装VMtool 1.1打开虚拟机之后-> 安装VMtool 1.2 点击之后,桌面就会出现一个VMtool光驱文件,如果提示光驱被占用就先用root登录 1.3在命令行挂载 sudo mo ...

  4. 树莓派 Raspberry Pi 4,.net core 3.0 ,Avalonia UI 开发

    虽说.net core3.0已经可以用于开发wpf和winform程序,可是遗憾的时目前这core下的wpf还是只能运行在windows下,想要在linux下运行wpf估计还要等一段时间. Avalo ...

  5. docker命令大全与资源汇总

    容器生命周期管理 run  //创建一个新的容器并运行一个命令 start/stop/restart  //启动一个或多个已经被停止的容器:停止一个运行中的容器:重启容器 kill  //杀掉一个运行 ...

  6. python基础知识(字符串)

    定义字符串 ' '单引号 " "双引号  只能用于单行 '" '"三引号  可以用于多行 拼接字符串使用  +号链接 字符串只能链接字符串其他类型字符串需要用s ...

  7. Jenkins 启动不来的排查方法

    1.通过 ps -ef | grep tomcat找到jenkins的路径,下有logs,可以查看日志 2.装插件报错时,报错信息里会提示依赖的插件版本号,到jenkins官网下载对应版本的插件即可, ...

  8. java学习-2

    类的定义 成员变量: 变量类型 变量名称 成员方法:public void 方法名称(){} 注意:成员变量是直接定义在类当中方法外面 创建对象使用类 导包.指出需要的类在什么位置 import 包名 ...

  9. PostgreSQL编码格式:客户端服务器、客户端、服务器端相关影响

    关于字符编码这块,官网链接: https://www.postgresql.org/docs/current/charset.html 刚刚写了几百字的东西因为断网,导致全没有了,重头再写,我就只想记 ...

  10. Oracle中分析函数

    1. row_number() over(PARTITION BY ...ORDER BY...)--在求第一名成绩的时候,不能用,因为如果有两个并列第一,只会返回一个 rank() over(PAR ...