9.1 使用流收集数据
第8章简要介绍了流。下面回顾一下流最重要的几个特征。
- 流并不存储元素。它们只处理存放在数据源(数据结构、文件等)中的元素。
- 流不可重用。
- 流可对数据进行延迟处理。
- 流操作不能修改流的源。
- 流允许你进行链式操作,因此一项操作的输出是下一项操作的输入。
流由下述三个要素构成。
- 生成流元素的源。
- 0个或多个中间操作,这些操作可以产生输出,形成另一个流。
- 一个可以产生结果的末端操作,该结果既可以是一个简单对象、数组、Colletion、Map,也可以是其他的东西。
Stream API提供了不同的末端操作,不过其中两个操作更加重要,它们具有更好的灵活性和更强的能力。在第8章中,你学会了如何使用reduce()方法,而在本章,将学会如何使用collect()方法。下面首先简单介绍一下该方法。
collect()方法
collect()方法可对流的元素进行转换和分组,生成一个含有流最终结果的新数据结构。你可以使用多达三种不同的数据类型:一种输入数据类型,即来自流的输入元素的数据类型;一种中间数据类型,用于在collect()方法运行过程中存放元素;以及一种输出数据类型,它由collect()方法返回。
collect()方法有两个版本。第一个版本接收下述三种函数型参数。
- Supplier函数:这是一个创建中间数据类型对象的函数。如果使用顺序流,该方法会被调用一次。如果使用并行流,该方法会被调用多次,而且每次都必须产生一个新对象。
- Accumulator函数:调用该函数可以处理输入元素,并且在中间数据结构中存放该元素。
- Combiner函数:调用该函数可以将两个中间数据结构合二为一。该函数只有在处理并行流时才会被调用。
这个版本的collect()方法用到了两种不同的数据类型:来自流的元素的输入数据类型,以及用于存放中间元素并返回最终结果的中间数据类型。
collect()方法的第二个版本接收一个实现Collector接口的对象。你可以自己实现该接口,但是使用Collector.of()静态方法更容易。该方法的参数如下所示。
- Supplier:该函数创建了一个中间数据类型的对象,其用法参照前面的介绍。
- Accumulator:调用该函数可以处理一个输入元素,如果必要还可对该元素进行转换,并且将其存放在中间数据结构中。
- Combiner:调用该函数可以将两个中间数据结构合并成一个,用法参照前面的介绍。
- Finisher:如果需要进行最终的转换或者计算,调用该函数可以将中间数据结构转换成最终的数据结构。
- Characteristics:可以使用这个最后的变量参数表明所创建的收集器的一些特征。
实际上,这两个版本之间存在稍许差别。带有三个参数的collect()方法接收的Combiner是BiConsumer,它必须将第二个中间结果合并到第一个中间结果中。而这一版本的collect()方法采用的Combiner是BinaryOperator,而且应该返回该Combiner。因此这一版本的Collect方法既可以选择将第二个中间结果合并到第一个,也可以将第一个中间结果合并到第二个,或者也可以创建一个新的中间结果。of()方法还有另一个版本,除了Finisher之外,参数都相同。在本例中,并不执行最终转换。
Java在Collector工厂类中提供了一些预定义的收集器。可以通过这些收集器的静态方法获得这些收集器。如下是其中的一些方法。
averagingDouble()、averagingInt()和averagingLong():这些方法返回一个收集器,能够计算double、int或者long型函数的算术平均值。groupingBy():该方法返回一个收集器,使你能够按照其对象的某一属性对流的元素进行分组,生成一个Map,其键为所选定属性的值,而其值为具有某一确定值的对象列表。groupingByConcurrent():这和前一个方法相似,只是有两点不同。第一个不同点在于该方法在并行模式下比groupingBy()方法更快,但是在顺序模式下却更慢。第二个(也是最重要的)不同点在于groupingByConcurrent()函数是一个无序的收集器。不能保证列表中项的顺序和其在流中的顺序相同。另一方面,groupingBy()收集器则能够保证排序。joining():该方法返回一个Collector工厂类,将输入元素串联为一个字符串。partitioningBy():该方法返回一个Collector工厂类,基于某个谓词的结果对输入元素进行划分。summarizingDouble()、summarizingInt()和summarizingLong():这些方法返回一个Collector工厂类,计算输入元素的汇总统计值。toMap():该方法返回一个Collector工厂类,使你可以基于两个映射函数将输入元素转换为一个Map。toConcurrentMap():该方法与前一个类似,只是以并发方式工作。在不考虑定制归并器的情况下,toConcurrentMap()只是在并行流的情况下较快。与groupingByConcurrent()方法一样,这也是一个无序收集器,而toMap()则采用相遇时的排序执行转换。toList():该方法返回一个Collector工厂类,将输入元素存放到一个列表中。toCollection():该方法使你能够按照相遇时的排序将输入元素累加到一个新的Collection工厂类(TreeSet、LinkedHashSet等)。该方法接收一个创建该Collection的Supplier接口实现作为参数。maxBy()和minBy():该方法返回一个Collector工厂类,根据以参数传递的比较器产生最大元素和最小元素。toSet():该方法返回一个Collector,它将输入元素存放到一个集合。
