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():这些方法返回一个收集器,能够计算doubleint或者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工厂类(TreeSetLinkedHashSet等)。该方法接收一个创建该Collection的Supplier接口实现作为参数。
  • maxBy()minBy():该方法返回一个Collector工厂类,根据以参数传递的比较器产生最大元素和最小元素。
  • toSet():该方法返回一个Collector,它将输入元素存放到一个集合。