6.1 收集器简介

前一个例子清楚地展示了函数式编程相对于指令式编程的一个主要优势:你只需指出希望的结果——“做什么”,而不用操心执行的步骤——“如何做”。在上一个例子里,传递给collect方法的参数是Collector接口的一个实现,也就是给Stream中元素做汇总的方法。上一章里的toList只是说“按顺序给每个元素生成一个列表”。在本例中,groupingBy说的是“生成一个Map,它的键是(货币)桶,值则是桶中那些元素的列表”。

要是做多级分组,指令式和函数式之间的区别就会更加明显:由于需要好多层嵌套循环和条件,指令式代码很快就变得更难阅读、更难维护和更难修改。相比之下,函数式版本只要再加上一个收集器就可以轻松地增强功能了,你会在6.3节中看到它。

6.1.1 收集器用作高级归约

刚刚的结论又引出了优秀的函数式API设计的另一个好处:更易复合和重用。收集器非常有用,因为用它可以简洁而灵活地定义collect用来生成结果集合的标准。更具体地说,对流调用collect方法将对流中的元素触发一个归约操作(由Collector来参数化)。图6-1所示的归约操作所做的工作和代码清单6-1中的指令式代码一样。它遍历流中的每个元素,并让Collector进行处理。

6.1 收集器简介 - 图1

图 6-1 按货币对交易分组的归约过程

一般来说,Collector会对元素应用一个转换函数(很多时候是不体现任何效果的恒等转换,例如toList),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。例如,在前面所示的交易分组的例子中,转换函数提取了每笔交易的货币,随后使用货币作为键,将交易本身累积在生成的Map中。

如货币的例子中所示,Collector接口中方法的实现决定了如何对流执行归约操作。6.5节和6.6节会研究如何创建自定义收集器。但Collectors实用类提供了很多静态工厂方法,可以方便地创建常见收集器的实例,只要拿来用就可以了。最直接和最常用的收集器是toList静态方法,它会把流中所有的元素收集到一个List中:

  1. List<Transaction> transactions =
  2. transactionStream.collect(Collectors.toList());

6.1.2 预定义收集器

本章剩下的部分主要探讨预定义收集器的功能,也就是那些可以从Collectors类提供的工厂方法(例如groupingBy)创建的收集器。它们主要提供了三大功能:

  • 将流元素归约和汇总为一个值;
  • 元素分组;
  • 元素分区。

先来看看可以进行归约和汇总的收集器。它们在很多场合下都很方便,比如前面例子中提到的求一系列交易的总交易额。

然后你将看到如何对流中的元素进行分组,同时把前一个例子推广到多层次分组,或把不同的收集器结合起来,对每个子组进行进一步归约操作。我们还将谈到分组的特殊情况——分区,即使用谓词(返回一个布尔值的单参数函数)作为分组函数。

6.4节节末有一张表,总结了本章中探讨的所有预定义收集器。在6.5节中你将了解更多有关Collector接口的内容。在6.6节中你会学到如何创建自己的自定义收集器,用于Collectors类的工厂方法无效的情况。