Pre
我们前面学到了,流可以用类似于数据库的操作帮助你处理集合。
它们支持两种类型的操作:
- 中间操作(如 filter 或 map )
- 终端操作(如 count 、 findFirst 、 forEach 和 reduce )
中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。
与此相反,终端操作会消耗流,以产生一个最终结果,例如返回流中的最大元素。它们通常可以通过优化流水线来缩短计算时间。
前面使用过了collect 终端操作了,当时主要是用来把 Stream 中所有的元素结合成一个 List 。
我们学习完收集器以后,会知道collect 是一个归约操作,就像 reduce 一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Collector 接口来定义的,因此区分 Collection 、 Collector 和 collect 是很重要的。
举几个例子,先感受下
- 对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个
Map
) - 将交易列表分成两组:贵的和不贵的(返回一个
Map>
) - 创建多级分组,比如按城市对交易分组,然后进一步按照贵的和不贵的分组(返回一个
Map>
)。
我们来看下不是用流,是如何处理的
如果用流呢?
简介
代码语言:javascript复制Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));
这个例子清晰的展示了函数式编程相对于指令式编程的一个主要优势:只需指出希望的结果—“做什么”,而不用操心执行的步骤——“如何做” 。
这个例子中传递给 collect方法的参数是 Collector 接口的一个实现,也就是给 Stream 中元素做汇总的方法。 我们之前接触的toList 只是说“按顺序给每个元素生成一个列表”; groupingBy 说的是“生成一个Map ,它的键是(货币)桶,值则是桶中那些元素的列表”。
要是做多级分组,指令式和函数式之间的区别就会更加明显:由于需要好多层嵌套循环和条件,指令式代码很快就变得更难阅读、更难维护、更难修改。相比之下,函数式版本只要再加上一个收集器就可以轻松地增强功能了。
收集器用作高级归约
对流调用collect 方法将对流中的元素触发一个归约操作(由 Collector 来参数化)。
看个归约操作的示例: 遍历流中的每个元素,并让 Collector 进行处理
一般来说, Collector 会对元素应用一个转换函数(很多时候是不体现任何效果的恒等转换例如 toList ),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。
例如,
代码语言:javascript复制Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream().collect(groupingBy(Transaction::getCurrency));
这个交易分组的例子中,转换函数提取了每笔交易的货币,随后使用货币作为键,将交易本身累积在生成的 Map 中。
Collector 接口中方法的实现决定了如何对流执行归约操作。但 Collectors 实用类提供了很多静态工厂方法,可以方便地创建常见收集器的实例,只要拿来用就可以了。最直接和最常用的收集器是 toList静态方法,它会把流中所有的元素收集到一个 List 中:
代码语言:javascript复制List<Transaction> transactions = transactionStream.collect(Collectors.toList());
预定义收集器
预定义收集器的功能,也就是那些可以从 Collectors类提供的工厂方法(例如 groupingBy )创建的收集器。
它们主要提供了三大功能:
- 将流元素归约和汇总为一个值
- 元素分组
- 元素分区
后续的博文我们继续对这几类展开学习~