在处理集合时,我们通常会遍历它的元素,并在每个元素上执行某项操作。例如,假设要对某个列表中所有长单词进行计数:
long count = 0;
for (String w : words) {
if (w.length() > 10) count ++;
}
使用流后,相同操作看起来像这样:
long count = words.stream()
.filter(w -> w.length() > 10)
.count();
流的版本比循环更易于阅读,因为我们不必扫描整个代码去查找过滤和计数操作,方法名就可以直接告诉我们意欲何为。而且循环需要详细指定操作的顺序,而流却能以其想要的任何方式来调度这些操作
仅将 stream 修改为 parallelStream 就可以让流库以并行方式来执行过滤和计数:
long count = words.parallelStream()
.filter(w -> w.length() > 10)
.count();
流遵循了 “做什么而非怎么做” 的原则。在上面的示例中,我们描述了需要做什么:获取长单词,并对其计数,而并没有指定该操作应该以什么顺序或在哪个线程中执行。相比之下,迭代操作就要确切指定应该如何工作
流表面上和集合类似,都可以让我们转换和获取数据。但是,它们之间存在着显著差异:
- 流并不存储元素。这些元素可能存储在底层的集合中,或者按需生成
- 流操作不会修改数据源。例如 filter 方法不会从新的流中移除元素,而是会生成一个新的流,其中不包含过滤掉的元素
- 流操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。仅仅调用到这类方法,并没有真正开始流的遍历
让我们再来看看这个示例。stream 和 parallelStream 方法会产生一个用于 words 列表的 stream。filter 方法会返回另一个流,其中只包含长度大于 10 的单词。count 方法会将这个流化简为一个结果
这个工作流是操作流时的典型。我们建立了一个包含三个阶段的操作管道:
- 创建一个流
- 指定将初始流转换为其他流的中间操作,可能包含多个步骤
- 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作。从此之后,这个流就再也不能用了