集合是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方式的代码一样。