我们都知道Lambda和Stream是Java8的两大亮点功能,我们在上一篇文章中介绍了Lambda相关的知识。本次介绍Java8的Stream流操作。它与java.io包的Input/OutputStream完全不同,也不是实时处理大数据的Stream。这个Stream操作是Java8对集合操作功能的增强,专注于对集合进行各种高效、便捷、优雅的聚合操作。借助Lambda表达式,编程效率和可读性得到显着提升。而且Stream提供了并行计算模式,可以简洁的编写并行代码,可以充分发挥当今计算机的多核处理优势。一、Stream简介Stream不同于其他的集合框架。它不是数据结构,也不保存数据,而是负责相关的计算,使用起来更像是一个高级迭代器。在之前的iterator中,我们只能遍历然后进行业务操作,而现在我们只需要指定要进行什么操作,Stream就会隐式遍历并进行想要的操作。另外,Stream和迭代器一样,只能单向处理,就像奔腾的长江之水,一去不复返。由于Stream提供了惰性计算和并行处理能力,在使用并行计算时,数据会自动分解成多个段并并行处理,最后将结果聚合。所以Stream操作可以让程序运行的更加高效。2.Stream的概念Stream的使用总是按照一定的步骤进行的,可以抽象出如下使用过程。数据源(source)->数据处理/转换(intermedia)->结果处理(terminal)2.1.数据源数据源(source)就是数据的来源。可以通过多种方式获取流数据源。下面介绍几种常见的获取方式。集合.stream();从集合中获取流。集合.parallelStream();从集合中获取并行流。Arrays.stream(T数组)或Stream.of();从数组中获取流。BufferedReader.lines();从输入流中获取一个流。IntStream.of();从静态方法获取流。流.生成();自己生成流2.2。数据处理数据处理/转换(intermedia)步骤可以有多个操作,这个步骤也称为中间操作(intermediaoperation)。这一步不管你怎么操作,它都会返回一个新的流对象,原来的数据不会有任何改变,而且这一步是惰性计算处理的,也就是说只调用方法不会开始处理,只有在真正开始收集结果的时候,中间操作才会生效,如果遍历没有完成,已经得到想要的结果(比如得到第一个值),就会停止遍历,得到结果被退回。惰性计算可以显着提高运行效率。数据处理演示。数据处理/转换操作自然不局限于上面演示的过滤filter和mapmapping这两种,还有map(mapToInt,flatMap等),filter,distinct,sorted,peek,limit,skip,parallel,sequential,无序等2.3。收集结果结果处理(终端)是流处理的最后一步。执行这一步后,stream会被完全耗尽,stream无法继续运行。只有到了这个操作,流数据处理/转换等中间过程才会开始计算,也就是上面说的惰性计算。结果处理也一定是流操作的最后一步。常见的结果处理操作包括forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator等,下面演示一个简单的结果处理示例。2.4.short-circuiting有一种Stream操作叫做short-circuiting,指的是当Stream流是无限的,但是需要返回的Stream流是有限的,希望它能在有限的时间内计算出结果,则此操作称为短路。例如 findFirst 操作。3.Stream的使用Stream在使用的时候总是借助于Lambda表达式进行操作。Stream的操作方式也有很多种。下面列出了11个常用操作。3.1.获取Stream上面的Stream数据源中介绍了几种获取Stream的方式。下面以使用上述几种获取Stream的方法为例。3.2.forEachforEach是Stream中的一个重要方法,用于遍历Stream,它支持传入一个标准的Lambda表达式。但是它的遍历不能被return/break终止。同时也是一个终端操作,执行完之后会消耗Stream流中的数据。比如输出对象。3.3.map/flatMap使用map将一个对象一对一地映射到另一个对象或表单。上面的map可以一对一的映射数据,有时候关系可能不是1对1那么简单,可能有1对多。这是可以使用flatMap的地方。下面演示使用flatMap来展平对象。3.4.filter使用过滤器过滤数据并选择所需的元素。以下示例演示如何选择偶数。获得以下结果。2、4、6、8、3.5。findFirstfindFirst可以找到Stream流中的第一个元素,它返回一个Optional类型。如果不知道Optional类的用处,可以参考上一篇。Jdk14出来了。还是不能使用Optional优雅地处理空指针?.findFirst方法找到需要的数据后,会返回,不再遍历数据。所以findFirst方法可以对无限数据的Stream流进行操作。也可以说findFirst是一个短路操作。3.6.collect/toArrayStream流可以很容易地转换为其他结构,下面是几个常见的例子。3.7.limit/skip获取或丢弃前n个元素3.8.Statistics数理统计函数,求一组数组的最大值、最小值、个数、数据总和、平均个数等。3.9.groupingBy分组聚合函数与数据库的Groupby函数一致。3.10.partitioningBy3.11。进阶——自己生成Stream上面的例子中,Stream是无限的,但是获取的结果是有限的,而Limit是用来限制获取的个数的,所以这个操作也是一个短路操作。4.Stream的优势4.1.简洁大方正确使用和格式化的流操作代码不仅简洁大方,而且赏心悦目。我们来比较一下使用Stream和不使用Stream时相同操作的编码风格。如果使用Stream流操作。4.2.Lazycalculation如前所述,数据处理/转换(intermedia)操作map(mapToInt、flatMap等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered等操作,在调用方法时就可以了不会立即调用,而是在实际使用的时候生效,这样可以延迟到真正需要的时候再进行操作。下面将给出一个例子来证明这一点。如果没有惰性计算,很明显先输出偶数,再输出分界线。而实际效果是。分界线246可以看出惰性计算延迟计算,直到真正需要的时候才计算。4.3.ParallelComputing在获取Stream流时,可以使用parallelStream方法代替stream方法获取并行处理流。并行处理可以在不增加编码复杂度的情况下充分发挥多核的优势。下面的代码演示了生成1000万个随机数后,每个随机数乘以2求和后,串行计算和并行计算的耗时差异。得到以下输出。效果明显,代码简洁大方。5.对Stream的建议5.1确保正确排版从上面的用例可以发现,使用Stream操作的代码非常简洁,可读性更强。但是如果排版不正确,就会显得很糟糕。比如下面这个功能相同的代码示例,多了多少层操作,是不是有点吃力?5.1保证函数纯度如果你想让你的Stream每次相同的操作都有相同的结果,那么你必须保证Lambda表达式的纯度,这就是下面的重点。Lambda中没有更改任何元素。Lambda不依赖于任何可能改变的元素。这两点对于保证函数的幂等性非常重要,否则你的程序的执行结果可能会变得不可预知,如下例所示。
