这段时间正好接触到Java8中的Stream,就想写点东西来简单记录下。
像我们学习其他知识一样,关于Stream,首先得弄清楚以下几点:
- What - 它是个什么东西(类?接口?),它在哪个包下,关系背景又怎样(这是只是有没有继承关系)
- Why - 为什么要用它,好处是什么…
- How - 怎么使用其实就没那么难了
接下来,就围绕上述问题来开始我们今天的讨论…论述如有不当之处,欢迎大家批评指正。
What
JDK文档有其的官方定义:
A sequence of elements supporting sequential and parallel aggregate operations.
官方这定义可谓精辟,首先,Stream代表的是一组有序的元素集合,然后支持串行和并行的聚集操作这一句要稍微解释下,这里的串行和并行指的是单任务和多任务去操作Stream(Stream的并行操作依赖于Java 7中引入的Fork/Join框架),从而加速处理过程,发挥多核处理器的优势。
Stream接口位于java.util.stream, 并且继承于BaseStream(父亲)。在java.util.stream中,我们可以发现她的3个兄弟姐妹,例如,DoubleStream, IntStream, LongStream,这里就不详细介绍其家人了,其实就是针对double,int,long,3种类型的流做了一些方法的封装,完全是可以通过原生Stream实现的。
Why
任何一个新生的接口都有其存在价值和意义,Stream也不例外。几乎任何Java应用都不可避免的会用到创建及处理集合数据。首先我们来看下在数据库中,常见的处理集合的方式,例如:
|
|
如上,我们需要找到其中身高168cm并且钱最多以上的MM,这里我们不需要写代码去实现查找Max的实现逻辑,只需要用Max
与 girl.height>=168
表述我们需要的就可以了。
那么问题来了,我们是否可以对Java中的集合(例如, List<Girl>
)进行类似操作呢?这里简单说明一下,并不是说Java里通过集合的操作不能实现上述功能,而是在于能否使用类似的代码编写的形式。
绕了一大圈,我们今天的主角Stream该登场了,它为Java的集合类提供了一种声明式的方式来操作数据。接下来我们再用一个例子来具体实现下,现已知一个Person的对象集合 allPersons,我们想要取出所有人中白富美的微信号,而且是按照资产数值由高到低排序:
In Java 7 or lower
|
|
In Java 8 by using Stream
|
|
机智如我的你,是不是已经发现这种编码方式很简洁,当然它也很高效(这里的高效并不会做很详细的解释),这里我引用来自另一篇文章的解释:
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
How
目前为止,我们对Stream存在的原因和优点有个大致的理解。接下来来看看如何去创建及使用Stream。
创建Stream
这里还是只介绍2种最常见的创建方式:
- Stream的静态方法
|
|
对于方法1,有2个重载方法;对于方法2,Stream则是通过给定的Supplier
接口(这个接口会在后续的文章中给大家介绍)来创建,最后的2行代码只是使用了lambda表达式和方法引用来简化代码。
- Collection子类的stream()方法
上文的例子中就演示了如何从List<Person>
对象中通过allPersons.stream()
获取其Stream对象。从Collection
的源码中就可以找到 stream()
方法的定义及实现(实现??? 对的,没错,当然这也是Java 8里新加的特性: Default Method, 所以它只需要定义在Collection
中,而不需要在其每个子类中完成实现),该方法的返回值类型就是Stream
操作Stream
在一个Stream对象被创建之后,接下来就是对其进行数据操作。一个数据流(Stream pipeline)是由数据源,若干中间方法(intermediate operation),一个汇聚方法(terminal operation)组成.
- 中间方法(intermediate operation)主要作用是完成数据的映射/过滤/排序等操作(形象点说就像数据流从一个管道,流入下一个管道,但是请注意数据流在流经中间管道时候自身并不会发生变化,只是记录了每个管道所对应的操作),这些操作的返回值都是
Stream
, 这就为链式(声明式)操作提供了基础。常见的操作有:filter()
,map()
,sorted()
,distinct()
,limit()
,skip()
- 汇聚方法(terminal operation)主要作用则是真正去处理和操作 (traverse,这里实在想不出一个很合适的词来形容,)整个Stream,例如,计算总值,查找最大值,将处理之后Stream中的集合元素封装成新的集合返回。在terminal operation后,Stream pipeline将会被关闭。常见的操作有:
count()
,foreach()
,collect()
,max()
,allMatch()
,anyMatch()
上述各种函数名是不是与SQL中的某些函数有似曾相识的感觉呢,也很容易理解各个方法的作用,这里就不再一一赘述。此外,操作方法的参数与大家平时常见的也有所不同,就拿上面例子中filter的参数来说p -> p.getGender() == Gender.FEMALE
, 这个就是传说中的lambda表达式(请切记,lambda表达式只是一种表达方式,其本质实际上为一个函数式接口Functional Interface
,它只是将这个接口的实现用lambda表达式的形式呈现出来),也是Java 8里一个添加的重要的特性。 可以看出,lambda表达式可以让我们更加灵活的去操作Stream,并且是我们的代码看上去更加简洁。
小结
本文旨在对Stream做一个入门介绍,涵盖了What,Why,How。当然,Stream所包含的东西远不止这些,建议大家有空还是去读一下官方文档。至此,相信大家也已经能够读懂之前查找白富美微信号的那段代码了吧。
下一篇文章将讨论Java 8的另一个重要内容 - Functional Interface