Java8实战-总结24
- 用流收集数据
- 收集器简介
- 收集器用作高级归约
- 预定义收集器
用流收集数据
流可以用类似于数据库的操作帮助你处理集合。可以把Java 8
的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作(如filter
或map
)和终端操作(如count
、findFirst
、forEach
和reduce
)。中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗流,以产生一个最终结果,例如返回流中的最大元素。它们通常可以通过优化流水线来缩短计算时间。
collect
是一个归约操作,就像reduce
一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Collector
接口来定义的,因此区分Collection
、Collector
和collect
是很重要的。
下面是一些查询的例子,看看用collect
和收集器能够做什么。
- 对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个
Map<Currency, Integer>
)。 - 将交易列表分成两组:贵的和不贵的(返回一个
Map<Boolean, List<Transaction>>
)。 - 创建多级分组,比如按城市对交易分组,然后进一步按照贵或不贵分组(返回一个
Map<Boolean, List<Transaction>>
)。
有一个由Transaction
构成的List
,并且想按照名义货币进行分组。在没有Lambda
的Java
里,哪怕像这种简单的用例实现起来都很啰嗦,就像下面这样,用指令式风格对交易按照货币分组:
//建立累积交易分组的Map
Map<Currency,List<Transaction>> transactionsByCurrencies = new HashMap<>();for(Transaction transaction : transactions) {Currency currency = transaction.getcurrency();//提取Transaction的货币List<Transaction> transactionsForCurrency = transactionsByCurrencies.get(currency);//如果分组Map中没有这种货币的条目,就创建一个if(transactionsForCurrency == null) {transactionsForCurrency = new ArrayList<>();transactionsByCurrencies.put(currency, transactionsForCurrency);}//将当前遍历的Transaction加入同一货币的Transaction的ListtransactionsForcurrency.add(transaction);
}
代码的目的并不容易看出来,尽管换作白话的话是很直截了当的:“把列表中的交易按货币分组。”用Stream
中collect
方法的一个更通用的Collector
参数,就可以用一句话实现完全相同的结果,而用不着使用toList
的特殊情况了:
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().collect (groupingBy(Transaction::getCurrency));
收集器简介
前一个例子清楚地展示了函数式编程相对于指令式编程的一个主要优势:只需指出希望的结果——“做什么”,而不用操心执行的步骤——“如何做”。在上一个例子里,传递给collect
方法的参数是Collector
接口的一个实现,也就是给Stream
中元素做汇总的方法。toList
只是说“按顺序给每个元素生成一个列表”;在本例中,groupingBy
说的是“生成一个Map
,它的键是(货币)桶,值则是桶中那些元素的列表”。
要是做多级分组,指令式和函数式之间的区别就会更加明显:由于需要好多层嵌套循环和条件,指令式代码很快就变得更难阅读、更难维护、更难修改。相比之下,函数式版本只要再加上一个收集器就可以轻松地增强功能了。
收集器用作高级归约
刚刚的结论又引出了优秀的函数式API设计的另一个好处:更易复合和重用。收集器非常有用,因为用它可以简洁而灵活地定义collect用来生成结果集合的标准。更具体地说,对流调用collect方法将对流中的元素触发一个归约操作(由Collector来参数化)。图6-1所示的归约操作所做的工作和代码清单6-1中的指令式代码一样。它遍历流中的每个元素,并让collector进行处理。
一般来说,Collector
会对元素应用一个转换函数(很多时候是不体现任何效果的恒等转换,例如toList
),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。例如,在前面所示的交易分组的例子中,转换函数提取了每笔交易的货币,随后使用货币作为键,将交易本身累积在生成的Map
中。
如货币的例子中所示,Collector
接口中方法的实现决定了如何对流执行归约操作。但Collectors
实用类提供了很多静态工厂方法,可以方便地创建常见收集器的实例,只要拿来用就可以了。最直接和最常用的收集器是toList
静态方法,它会把流中所有的元素收集到一个List中:
List<Transaction> transactions = transactionstream.collect(Collectors.toList());
预定义收集器
预定义收集器,也就是那些可以从Collectors
类提供的工厂方法(例如groupingBy
)创建的收集器。它们主要提供了三大功能:
- 将流元素归约和汇总为一个值
- 元素分组
- 元素分区
先来看看可以进行归约和汇总的收集器。它们在很多场合下都很方便,比如前面例子中提到的求一系列交易的总交易额。
然后将看到如何对流中的元素进行分组,同时把前一个例子推广到多层次分组,或把不同的收集器结合起来,对每个子组进行进一步归约操作。还将谈到分组的特殊情况“分区”,即使用谓词(返回一个布尔值的单参数函数)作为分组函数。