第 6 章 用流收集数据
本章内容
- 用
Collectors类创建和使用收集器- 将数据流归约为一个值
- 汇总:归约的特殊情况
- 数据分组和分区
- 开发你的自定义收集器
我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合。你可以把Java 8的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作(如filter或map)和终端操作(如count、findFirst、forEach和reduce)。中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗流,以产生一个最终结果,例如返回流中的最大元素。它们通常可以通过优化流水线来缩短计算时间。
我们已经在第4章和第5章中用过collect终端操作了,当时主要是用来把Stream中所有的元素结合成一个List。在本章中,你会发现collect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Collector接口来定义的,因此区分Collection、Collector和collect是很重要的。
下面是一些查询的例子,看看你用collect和收集器能够做什么。
- 对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个
Map)。 - 将交易列表分成两组:贵的和不贵的(返回一个
Map)。> - 创建多级分组,比如按城市对交易分组,然后进一步按照贵或不贵分组(返回一个
Map)。>>
激动吗?很好,先来看一个利用收集器的例子。想象一下,你有一个由Transaction构成的List,并且想按照名义货币进行分组。在Java 8之前,哪怕像这种简单的用例实现起来都很啰唆,就像下面这样。
代码清单 6-1 用指令式风格对交易按照货币分组
Map<Currency, List<Transaction>> transactionsByCurrencies =new HashMap<>(); ←---- 建立累积交易分组的Mapfor (Transaction transaction : transactions) { ←---- 迭代Transaction的ListCurrency currency = transaction.getCurrency(); ←---- 提取Transaction的货币List<Transaction> transactionsForCurrency =transactionsByCurrencies.get(currency);if (transactionsForCurrency == null) { ←---- 如果分组Map中没有这种货币的条目,就创建一个transactionsForCurrency = new ArrayList<>();transactionsByCurrencies.put(currency, transactionsForCurrency);}transactionsForCurrency.add(transaction); ←---- 将当前遍历的Transaction加入同一货币的Transaction的List}
如果你是一位经验丰富的Java程序员,那写这种东西可能挺顺手的,不过你必须承认,做这么简单的一件事就得写很多代码。更糟糕的是,读起来比写起来更费劲!代码的目的并不容易看出来,尽管换作白话是很直截了当:“把列表中的交易按货币分组。”你在本章中会学到,用Stream中collect方法的一个更通用的Collector参数,就可以用一句话实现完全相同的结果,而用不着使用上一章中那个toList的特殊情况了:
Map<Currency, List<Transaction>> transactionsByCurrencies =transactions.stream().collect(groupingBy(Transaction::getCurrency));
这一比差得还真多,对吧?
