本章内容
口用co1lectors类创建和使用收集器
口将数据流归约为一个值
口汇总:归约的特殊情况
数据分组和分区口
口 开发自己的自定义收集器
我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合。你可以把Java8的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作(如fi1ter或map)和终端操作(如count、findrirst、forEach和reduce)。中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗流,以产生一个最终结果,例如返回流中的最大元素。它们通常可以通过优化流水线来缩短计算时间。
我们已经在第4章和第5章中用过co11ect终端操作了,当时主要是用来把stream中所有的元素结合成一个List。在本章中,你会发现co1lect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Co1lector接口来定义的,因此区分collection、collector和co1lect是很重要的。下面是一些查询的例子,看看你用collect和收集器能够做什么。口对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个Map<currency,Integer>)口将交易列表分成两组:贵的和不贵的(返回一个Map<Boolean,List<Transaction>>)。口创建多级分组,比如按城市对交易分组,然后进一步按照贵或不贵分组(返回一个Map<Boolean,List<Transaction>>激动吗?很好,我们先来看一个利用收集器的例子。想象一下,你有一个由Transaction构成的List,并且想按照名义货币进行分组。在没有Lambda的Java里,哪怕像这种简单的用例实现起来都很啰嗦,就像下面这样。
6.1 收集器简介
前一个例子清楚地展示了函数式编程相对于指令式编程的一个主要优势:你只需指出希望的结果--“做什么”,而不用操心执行的步骤--“如何做”。在上一个例子里,传递给collect方法的参数是co1lector接口的一个实现,也就是给stream中元素做汇总的方法。上一章里的toList只是说“按顺序给每个元素生成一个列表”;在本例中,groupingBy说的是“生成一个Map,它的键是(货币)桶,值则是桶中那些元素的列表”。
要是做多级分组,指令式和函数式之间的区别就会更加明显:由于需要好多层嵌套循环和条件,指令式代码很快就变得更难阅读、更难维护、更难修改。相比之下,函数式版本只要再加上一个收集器就可以轻松地增强功能了,你会在6.3节中看到它。
6.1.1 收集器用作高级归约
刚刚的结论又引出了优秀的函数式API设计的另一个好处:更易复合和重用。收集器非常有用,因为用它可以简洁而灵活地定义co1lect用来生成结果集合的标准。更具体地说,对流调用co1lect方法将对流中的元素触发一个归约操作(由co1lector来参数化)。图6-1所示的归约操作所做的工作和代码清单6-1中的指令式代码一样。它遍历流中的每个元素,并让collector进行处理。
一般来说,collector会对元素应用一个转换函数(很多时候是不体现任何效果的恒等转换,例如toList),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。例如,在前面所示的交易分组的例子中,转换函数提取了每笔交易的货币,随后使用货币作为键,将交易本身