JDK1.8新特性(五):Stream,集合操作利器,让你好用到飞起来
集合是Java中使用最多的API,几乎每个程序员天天都会和它打招呼,它可以让你把相同、相似、有关联的数据整合在一起,便于使用、提取以及运算等操作。在实际Java程序中,集合的使用往往随着业务需求、复杂度而变得更加复杂,在这其中将可能会涉及到更多的运算,如:求和、平均值、分组、过滤、排序等等。如何这些操作混合出现,又该如何实现?难道遍历、再遍历、再运算么?抛开性能因素,这些操作已经严重影响了代码的整洁,这种代码也没有几个人愿意来读。
那么,有没有什么好的办法来解决这种现状呢?毕竟集合最为最常用的操作,难道Java语言的设计者没有意识到这一点吗?如何能够帮助你节约宝贵的时间,让程序员活得更轻松一点呢?
你可能已经猜到了,答案就是流—Stream。
本文将从JDK1.8
中Stream API
讲起,让你觉得集合操作原来可以这么轻松使用。
(在学习本节之前,必须先学习Lambda表达式相关知识,不清楚的可以翻看前几篇文章JDK1.8新特性(三):Lambda表达式,让你爱不释手、JDK1.8新特性(四):函数式接口)
一、Stream是什么
Stream
是Java API
中的新成员,它允许你以声明的方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现),你可以把它看成是遍历数据集的高级迭代器。此外,Stream还可以透明地并行处理,而无需写任何多线程代码了。
我们先简单的对比使用下Stream的好处吧。下面两段代码都是实现筛选出名字中包含“xc”字符串的人,并按照其年龄进行排序。
传统方式(JDK1.8之前,非Stream流):
List<people> peoples = new ArrayList<>(); // 遍历 + 判断 for (People people : allPeoples) { if (people.getName().contains("xc")) { peoples.add(people); } } // 对年龄排序 Collections.sort(peoples, new Comparator<people>() { @Override public int compare(People p1, People p2) { return Integer.compare(p1.getAge(), p2.getAge()); } });
Stream方式:
List<people> peoples2 = allPeoples.stream() .filter(people -> people.getName().contains("xc")) .sorted(Comparator.comparing(People::getAge)) .collect(Collectors.toList());
没有对比就没有伤害,效果显而易见。从开发角度来看,Stream方式有以下显而易见的好处:
-
代码以声明方式写的:说明想要完成什么(筛选出满足条件的数据)而不是说明如何实现一个操作(利用循环和if条件等控制流语句)。
-
多个基本操作链接起来:将多个基础操作链接起来,来表达复杂的数据处理流水线(如下图),同时体现了代码的清晰、可读性。
Stream API
功能非常强大,类似上面Stream处理流水线方式应用场景很多,理论上可以生成一个具有无穷长的流水线的。更重要的是,在复杂业务中你用不着为了让某些数据处理任务并行而去操心线程和锁了,Stream API
都替你做好了!
Stream
,即:”流“,通过将集合转换为一种叫做”流“的元素序列,通过声明方式,对集合中的每个元素进行一系列并行或串行的流水线操作。
换句话说,你只需要告诉流你的要求,流便会在背后自行根据要求对元素进行处理,而你只需要 “坐享其成”。
二、Stream操作
整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理,如下图所示。
其中,数据源是原始集合数据,然后将如 List<t>
的集合转换为Stream<t>
类型的流,并对流进行一系列的操作,比如过滤保留部分元素、对元素进行排序、类型转换等,最后再进行一个终止操作,可以把 Stream 转换回集合类型,也可以直接对其中的各个元素进行处理,比如打印、比如计算总数、计算最大值等。
很多流操作本身就会返回一个流,所以多个操作可以直接连接起来,就如上面举例中Stream方式的代码一样。
如果是以前,进行这么一系列操作,你需要做个迭代器或者 foreach 循环,然后遍历,一步步地亲力亲为地去完成这些操作。但是如果使用流,你便可以直接声明式地下指令,流会帮你完成这些操作。
通过上面Stream操作流水线、实例,Stream操作大体上分为两种:中间操作符和终止操作符。
1. 中间操作符
对于数据流来说,中间操作符在执行指定处理逻辑后,数据流依然可以传递给下一级的操作符。
中间操作符包含8种:
-
map(mapToInt,mapToLong,mapToDouble)
转换操作:把比如A->B
,这里默认提供了转int
,long
,double
的操作符。 -
flatmap(flatmapToInt,flatmapToLong,flatmapToDouble)
拍平操作:比如把int[]{2,3,4}
拍平变成2,3,4
,也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int
,long
,double
的操作。 -
limit
限流操作:比如数据流中有10个,我只要前3个就可以使用。 -
distinct
去重操作:重复元素去重。 -
filter
过滤操作:对集合数据进行过滤。 -
peek
消费操作:如果想对数据进行某些操作,如:读取、编辑修改等。 -
skip
跳过操作:跳过某些元素。 -
sorted
排序操作:对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
(具体可参照源码java.util.stream.Stream
)
2. 终止操作符
数据经过一系列的中间操作,就轮到终止操作符上场了。终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。
-
collect
收集操作:将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream的核心在于Collectors。 -
count
统计操作:统计最终的数据个数。 -
findFirst、findAny
查找操作:查找第一个、查找任何一个,返回的类型为Optional。 -
noneMatch、allMatch、anyMatch
匹配操作:数据流中是否存在符合条件的元素,返回值为bool 值。 -
min、max
最值操作:需要自定义比较器,返回数据流中最大、最小的值。 -
reduce
规约操作:将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。 -
forEach、forEachOrdered
遍历操作:这里就是对最终的数据进行消费了。 -
toArray
数组操作:将数据流的元素转换成数组。
说了这么多,心动不如行动,俗话说:实践出真理。那么,一起来实战吧。
三、实战演练
Stream的一系列操作,必须要使用终止操作符,否则整个数据流是不会执行起来的。
1. map
转换、映射操作,将元素转换成其他形式或提取一些信息。
比如,从People集合中获取所有人的年龄:
allPeoples.stream() .map(People::getAge) .forEach(System.out::println);
2. flatmap
将元素拍平拍扁 ,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream。
比如,带有-字符的字符串进行拆分,并输出:
Stream.of("x-c-b-e-y-o-n-d","a-b-c-d") .flatMap(m -> Stream.of(m.split("-"))) .forEach(System.out::println);
输出:
x c b e y o n d a b c d
3. limit
限流操作,限制集合元素的个数。
比如,集合中有10个元素,我只要前4个就可以使用:
Stream.of(1,2,3,4,5,6,7,8,9,10) .limit(4) .forEach(System.out::println);
输出:
1 2 3 4
4. distinct
去重操作,重复元素去重,类似数据库中的关键字distinct
。
比如,集合中可能存在一些重复的数据,需要进行去重操作:
Stream.of("xcbeyond","Niki","Liky","xcbeyond") .distinct() .forEach(System.out::println);
输出:
xcbeyond Niki Liky
5. filter
过滤、筛选,对某些元素进行过滤,不符合筛选条件的将无法进入流的下游。
比如,筛选出一个数字集合中的所有偶数:
Stream.of(1,2,3,4,5,6,7,8,9,10) .filter(n -> 0 == n%2) .forEach(System.out::println);
输出:
2 4 6 8 10
6. peek
消费操作,如果想对数据进行某些操作,如:读取、编辑修改等。
比如,将People集合中name统一修改为name+age的形式:
allPeoples.stream() .peek(people -> people.setName(people.getName() + people.getAge())) .forEach(people -> System.out.println(people.getName()));
7. skip
跳过操作,跳过某些元素。
比如,一个数字集合,跳过前4个元素:
Stream.of(1,2,3,4,5,6,7,8,9,10) .skip(4) .forEach(System.out::println);
输出:
5 6 7 8 9 10
8. sorted
排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
比如,将People集合按照年龄排序:
allPeoples.stream() .sorted(Comparator.comparing(People::getAge)) .forEach(System.out::println);
9. collect
收集操作,终止操作符,用于将最终的数据收集到新的集合中,如,List、Set、Map等集合。
比如,将People集合按照年龄排序,并存放在一个新的集合中,供后续使用:
List<people> sortedPeople = allPeoples.stream() .sorted(Comparator.comparing(People::getAge)) .collect(Collectors.toList());
10. count
统计操作,用于对集合元素个数的统计,返回类型是long。
比如,计算People集合中年龄大于30的人数:
long count = allPeoples.stream() .filter(people -> people.getAge() > 30) .count();
11. findFirst、findAny
查找操作,查找第一个、任何一个,返回的类型为Optional
。常用于查询集中符合条件的元素,并结合Optional.isPresent()
进行判断,防止出现未找到而强制获取数据元素的异常情况。
比如,查找People集合中名字为xcbeyond的人:
People xcbeyondPeople = null; Optional<people> optional = allPeoples.stream() .filter(people -> "xcbeyond".equals(people.getName())) .findFirst(); if (optional.isPresent()) { xcbeyondPeople = optional.get(); }
12. noneMatch、allMatch、anyMatch
匹配操作,判断数据流中是否存在符合条件的元素,返回值为boolean
值。
-
noneMatch:没有匹配条件的元素
-
allMatch、anyMatch:全匹配
比如,判断People集合中是否有名字为xcbeyond的人:
boolean bool = allPeoples.stream() .allMatch(people -> "xcbeyond".equals(people.getName()));
13. min、max
最值操作,根据自定义比较器,返回数据流中最大、最小的元素。
比如,找到People集合中最大年龄的人:
People maxAgePeople = null; Optional<people> maxAgeOptional = allPeoples.stream() .max(Comparator.comparing(People::getAge)); if (maxAgeOptional.isPresent()) { // 可能没有,则需要进行判断 maxAgePeople = maxAgeOptional.get(); }
14. reduce
规约操作:将整个数据流的值规约为一个值,其中count、min、max底层就是使用reduce。
比如,对一个整数集合进行求和:
int sum = Stream.of(1,9,8,4,5,6,-1) .reduce(0,(e1,e2)->e1+e2); System.out.println(sum);
输出:
32
四、总结
本文就Stream的基础使用层面进行了全面的介绍、实战,告诉你该怎么用每种操作符,只有掌握了这些基本的操作,在面对实际复杂处理逻辑时,需要进一步配合使用,就会知道它的妙处了。这也让你对集合的操作更上一步,为你省去了不少麻烦。关于Stream更深入的说明,如:并行处理、是否高效等,将会在之后的章节进行详尽的阐述、验证,以消除使用中的疑惑与担忧。
下一篇,将会提到Stream的终极操作——Collectors,让集合也能像数据库SQL一样,来完成复杂的分组、连接、汇总等,真正让你好用到飞起来。
</people></people></people></t></t></people></people></people>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Serverless:这真的是未来吗?(一)
原文 | https://www.pulumi.com/blog/is_serverless_the_future_part_1/ 作者 | Lee Briggs & Piers Karsenbarg 译者 | donghui 许多开发人员说,无服务器是计算的未来,而其他开发人员说,它永远不会成功。我们自己的观点没有那么两极分化。我们将无服务器视为一种选择,这是从初创企业到中型企业,再到大型企业的一个可能的垫脚石。在这两篇博文中,我们将讨论无服务器如何适应这一过程,以及它的优点和缺点。 我们的目标是帮助您切实地评估无服务器计算。我们希望激发讨论,而不是下意识的反应,无论是赞成还是反对。希望这些博客文章能帮助您在所有相关人员中展开讨论,就最佳业务方案达成一致。该课程可能涉及无服务器,也可能不涉及。在这第一篇文章中,我们将考虑在讨论无服务器时最常见的几个问题。在第二篇文章中,我们将研究一些更广泛的问题。 什么是无服务器? “无服务器"这个术语有点用词不当。更愤世嫉俗的人可能会嘀咕,“无服务器仍然在服务器上运行!“这是真的。不管你使用什么云提供商,你总是使用服务器来运行你的应用程序。...
- 下一篇
EMQ X v4.3 正式发布:性能大幅提升,更好用的多语言扩展
连接海量物联网设备的 MQTT 消息服务器 - EMQ X 是基于高并发的 Erlang/OTP 语言平台开发,支持百万级连接和分布式集群架构。EMQ X 已经在全球物联网市场广泛应用,无论是产品原型设计、物联网创业公司、还是大规模的商业部署,都支持免费使用。 产品介绍:https://www.emqx.cn/products/broker 下载地址:https://www.emqx.cn/downloads#broker 概览 EMQ X v4.3.0 版本修复了一些已知问题并新增了诸多特性,在通配符订阅性能、路由表内存占用、规则引擎性能以及大量客户端离线处理性能方面做了较大改进,同时将 多语言 扩展底层通信方式由 erlport 更换为更为灵活的 gRPC 通信。 该版本是 4.x 最后一个次要版本,旨在于为用户提供一个功能丰富、性能稳定的 MQTT 服务器,推荐所有 4.x 用户升级到此版本,此后 4.x 版本将进入维护状态,EMQ X Team 将进入到后续 5.0 版本开发工作中。 访问 EMQ X GitHub 仓库,点击右上角的 Watch 即可关注 EMQ X 5.0 ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS关闭SELinux安全模块
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作