Java 8 API添加了一个新的抽象称为流Stream
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
什么是 Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
一、生成流
从集合生成流:
在 Java 8 中, 集合接口有两个方法来生成流:
-
stream() − 为集合创建串行流。
-
parallelStream() − 为集合创建并行流。
二、中间操作
forEach
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
映射:
SELECT
关键字后面添加需要的字段名称,可以仅输出我们需要的字段数据,而流式处理的映射操作也是实现这一目的,在java8的流式处理中,主要包含两类映射操作:map和flatMap。
map
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
.filter(student -> "计算机科学".equals(student.getMajor()))
.map(Student::getName).collect(Collectors.toList());
mapToDouble(ToDoubleFunction
,mapToInt(ToIntFunction
,mapToLong(ToLongFunction
,这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有专业为计算机科学学生的年龄之和,那么我们可以实现如下:
.filter(student -> "计算机科学".equals(student.getMajor()))
.mapToInt(Student::getAge).sum();
flatmap
flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流。
举例说明,假设我们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};
,我们希望输出构成这一数组的所有非重复字符,那么我们可能首先会想到如下实现:
List distinctStrs = Arrays.stream(strs)
.map(str -> str.split("")) // 映射成为Stream .distinct()
.collect(Collectors.toList());
在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:
[j, a, v, a, 8][i, s][e, a, s, y][t, o][u, s, e]
distinct只有对于一个包含多个字符的流进行操作才能达到我们的目的,即对Stream
进行操作。此时flatMap就可以达到我们的目的:
List distinctStrs = Arrays.stream(strs)
.map(str -> str.split("")) // 映射成为Stream .flatMap(Arrays::stream) // 扁平化为Stream .distinct()
.collect(Collectors.toList());
flatMap将由map映射得到的Stream
,转换成由各个字符串数组映射成的流Stream
,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam
,从而能够达到我们的目的。
与map类似,flatMap也提供了针对特定类型的映射操作:flatMapToDouble(Function)
,flatMapToInt(Function)
,flatMapToLong(Function)
。
过滤:
filter
Predicate
,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate
是一个函数式接口,其包含一个test(T t)
方法,该方法返回boolean
。以下代码片段使用 filter 方法过滤出空字符串:
distinct
distinct操作类似于我们在写SQL语句时,添加的DISTINCT
关键字,用于去重处理,distinct基于Object.equals(Object)
实现,假设我们希望筛选出所有不重复的偶数,那么可以添加distinct操作:
List evens = nums.stream()
.filter(num -> num % 2 == 0).distinct()
.collect(Collectors.toList());
limit
limit 方法用于获取指定数量的流。limit操作也类似于SQL语句中的LIMIT
关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,以下代码片段使用 limit 方法打印出 10 条数据:
说到limit,不得不提及一下另外一个流操作:
sorted
Comparable
接口,如果没有实现也不要紧,我们可以将比较器作为参数传递给
sorted(Comparator)
。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:
skip
skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素。通过skip,就会跳过前面两个元素,返回由后面所有元素构造的流,如果n大于满足条件的集合的长度,则会返回一个空的集合。以下代码片段使用 skip方法对输出的 10 个随机数取排序在2之后的数:
三、终端操作
终端操作是流式处理的最后一步,我们可以在终端操作中实现对流查找、归约等操作。
1.查找
allMatch
allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true
boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);
anyMatch
anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true
boolean hasWhu = students.stream().anyMatch(student -> "武汉大学".equals(student.getSchool()));
noneMatch
findFirst
findFirst用于返回满足条件的第一个元素
findFirst不携带参数,具体的查找条件可以通过filter设置,此外我们可以发现findFirst返回的是一个Optional类型,关于该类型的具体讲解可以参考上一篇:Java8新特性 - Optional类。http://www.zhenchao.org/2016/09/24/java8-optional/
findAny
2.归约
collect(Collectors.toList())
对数据封装返回,如我的目标不是返回一个新的集合,而是希望对经过参数化操作后的集合进行进一步的运算,那么我们可用对集合实施归约操作。java8的流式处理提供了
reduce
方法来达到这一目的。
并行(parallel)程序
parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:
我们可以很容易的在顺序运行和并行直接切换。
四、并行流式数据处理
parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:
流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()
方法来启动流式处理,java8还提供了parallelStream()
来启动并行流式处理,parallelStream()
本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。
启动并行流式处理虽然简单,只需要将stream()
替换成parallelStream()
即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要。
Collectors
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。