4.2 流简介
讨论流之前,先来聊聊集合,这可能是最容易上手的方式了。Java 8的集合支持一个新的stream方法,它返回一个流(接口定义在java.util.stream.Stream中)。后面你会看到,还有很多别的方法也可以返回流,比如利用数值范围或从I/O资源生成流元素。
那么,流到底是什么?简短的定义就是“从支持数据处理操作的源生成的元素序列”。让我们一步步剖析这个定义。
- 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如
ArrayList与LinkedList)。但流的目的在于表达计算,比如你前面见到的filter、sorted和map。集合讲的是数据,流讲的是计算。后面几节会详细解释这个思想。 - 源——流会使用一个提供数据的源,比如集合、数组或I/O资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
- 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,比如
filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可以并行执行。
此外,流操作有两个重要的特点。
- 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,构成一个更大的流水线。这使得下一章中将要讨论的一些优化成为可能,比如处理延迟和短路。流水线的操作可以看作类似对数据源进行数据库查询。
- 内部迭代——与集合使用迭代器进行显式迭代不同,流的迭代操作是在后台进行的。第1章中简要提到过这一点,下一节还会再谈到它。
下面来看一段能够体现所有这些概念的代码:
import static java.util.stream.Collectors.toList;List<String> threeHighCaloricDishNames =menu.stream() ←---- 从menu(菜肴列表)获得流.filter(dish -> dish.getCalories() > 300) ←---- 建立操作流水线:首先选出高热量的菜肴.map(Dish::getName) ←---- 获取菜名.limit(3) ←---- 只选择头三个.collect(toList()); ←---- 将结果保存在另一个List中System.out.println(threeHighCaloricDishNames); ←---- 结果是[pork, beef, chicken]
本例先是对menu调用stream方法,由菜单得到一个流。数据源是菜肴列表(菜单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit和collect。除了collect之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流水线,于是就可以看作对源的一个查询。最后,collect操作开始处理流水线,并返回结果(它和别的操作不一样,因为它返回的不是流,在这里是一个List)。在调用collect之前,没有任何结果产生,实际上根本就没有从menu里选择元素。你可以这么理解:链中的方法调用都在排队等待,直到调用collect。图4-2显示了流操作的顺序:filter、map、limit、collect,每个操作简介如下。
filter——接受一个Lambda,从流中排除某些元素。在本例中,通过传递Lambdad -> d.getCalories() > 300,选择出热量超过300卡路里的菜肴。map——接受一个Lambda,将元素转换成其他形式或提取信息。在本例中,通过传递方法引用Dish::getName,相当于Lambdad -> d.getName(),提取了每道菜的菜名。limit——截断流,使其元素不超过给定数量。collect——将流转换为其他形式。在本例中,流被转换为一个列表。它看起来有点儿像变魔术,第6章会详细解释collect的工作原理。现在,你可以把collect看作能够接受各种方案作为参数,并将流中的元素累积成为一个汇总结果的操作。这里的toList()就是将流转换为列表的方案。

图 4-2 使用流来筛选菜单,找出三个高热量菜肴的名字
注意看,刚刚解释的这段代码,与逐项处理菜单列表的代码有很大不同。首先,我们使用了声明性的方式来处理菜单数据,即你说的对这些数据需要做什么:“查找热量最高的三道菜的菜名。”你并没有去实现筛选(filter)、提取(map)或截断(limit)功能,Streams库已经自带了。因此,Stream API在决定如何优化这条流水线时更为灵活。例如,筛选、提取和截断操作可以一次进行,并在找到这三道菜后立即停止。下一章会介绍一个能体现这一点的例子。
在进一步介绍能对流做什么操作之前,先回过头来看看Collection API和新的Stream API的概念有何不同。
