目录检索
为什么需要 Stream
Java8中的stream与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念,也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream,它是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
Stream的特性
在集合和其他数据集上运行函数式查询。Stream可以认为是一个高级版本的Iterator,它代表着数据流,流中的数据元素的数量可以是有限的,也可以是无限的。其差别在于:
无存储:Stream是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
函数式编程:对Stream的任何修改都不会修改背后的数据源,比如对Sream的filter操作并不会删除被过滤掉的元素,而是生成一个不含被过滤元素的新的Stream。
延迟执行:Stream由一个或多个中间操作(intermediate operation)和一个结束操作(terminal operation)两部分组成。只有执行了结束操作,Stream定义的中间操作才会依次执行,这就是Stream的延迟特性。
可消费性:Stream只能被“消费”一次,一旦遍历过就会失效。就像容器的迭代器那样,想要再次遍历必须重新生成一个新的Stream。
流管道剖析
JDK 中的流来源
方法 | 描述 |
---|---|
Collection.stream() | 使用一个集合的元素创建一个流 |
Stream.of(T…) | 使用传递给工厂方法的参数创建一个流 |
Stream.of(T[]) | 使用一个数组的元素创建一个流 |
… | … |
中间流操作
操作 | 描述 |
---|---|
filter(Predicate) | 与预期匹配的流的元素 |
map(Function<T, U>) | 将提供的函数应用于流的元素的结果 |
flatMap(Function<T, Stream> | 将提供的流处理函数应用于流元素后获得的流元素 |
distinct() | 已删除了重复的流元素 |
sorted() | 按自然顺序排序的流元素 |
Sorted(Comparator) | 按提供的比较符排序的流元素 |
limit(long) | 截断至所提供长度的流元素 |
skip(long) | 丢弃了前 N 个元素的流元素 |
takeWhile(Predicate) | (仅限 Java 9)在第一个提供的预期不是 true 的元素处阶段的流元素 |
dropWhile(Predicate) | (仅限 Java 9)丢弃了所提供的预期为 true 的初始元素分段的流元素 |
中间操作始终是惰性的:调用中间操作只会设置流管道的下一个阶段,不会启动任何操作。重建操作可进一步划分为无状态 和有状态 操作。无状态操作(比如 filter() 或 map())可独立处理每个元素,而有状态操作(比如 sorted() 或 distinct())可合并以前看到的影响其他元素处理的元素状态。
终止流操作
操作 | 描述 |
---|---|
forEach(Consumer action) | 将提供的操作应用于流的每个元素。 |
toArray() | 使用流的元素创建一个数组。 |
reduce(…) | 将流的元素聚合为一个汇总值。 |
collect(…) | 将流的元素聚合到一个汇总结果容器中。 |
min(Comparator) | 通过比较符返回流的最小元素。 |
max(Comparator) | 通过比较符返回流的最大元素。 |
count() | 返回流的大小。 |
{any,all,none}Match(Predicate) | 返回流的任何/所有元素是否与提供的预期相匹配。 |
findFirst() | 返回流的第一个元素(如果有)。 |
findAny() | 返回流的任何元素(如果有)。 |
附加信息
大多数流操作都要求传递给它们的拉姆达表达是互不干扰和无状态的。互不干扰意味着它们不会修改流来源;无状态意味着它们不会访问(读或写)任何可能在流操作寿命内改变的状态。对于缩减操作(例如计算 sum、min 或 max 等汇总数据),传递给这些操作的拉姆达表达式必须是结合性 的(或遵守类似的要求)。
如下使用有状态拉姆达表达式的流管道(不要这么做!)
HashSet<Integer> twiceSeen = new HashSet<>();
int[] result
= elements.stream()
.filter(e -> {
twiceSeen.add(e * 2);