收集器可以简洁而灵活地定义collect用来生成结果集合的标准。更具体地说,对流调用 collect 方法将对流中的元素触发一个归约操作(由Collector来参数化)。一般来说,Collector 会对元素应用一个转换函数(很多时候是不体现任何效果的恒等转换, 例如 toList ),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。下面就来学习那些可以从Collectors 类提供的工厂方法(例如groupingBy)创建的收集器。
collect 方法使用收集器来完成复杂的操作,方法定义如下:
<R, A> R collect(Collector<? super T, A, R> collector);Java 8 标准库提供了一些常用的收集器,由 java.util.stream.Collectors 静态类提供。
为了方便收集器的使用,Java提供了 Collectors 类,其包含众多常用的收集器工厂方法。主要提供了三大功能:
将流元素归约和汇总为一个值元素分组元素分区计算苹果的数量,功能同 count()
appleList.stream().collect(Collectors.counting()); 同 appleList.stream().count();假设我们要找出重量最大和最小的苹果,我们可以使用两个收集器, Collectors.maxBy 和Collectors.minBy ,来计算流中的最大或最小值。这两个收集器接收一个 Comparator 参数来比较流中的元素。
//比较器 Comparator<Apple> appleComparator = Comparator.comparingInt(Apple::getWeight); //最大值 Optional<Apple> maxApple = appleList.stream().max(appleComparator); Optional<Apple> maxApple2 = appleList.stream().collect(Collectors.maxBy(appleComparator)); //最小值 Optional<Apple> minApple = appleList.stream().min(appleComparator); Optional<Apple> minApple2 = appleList.stream().collect(Collectors.minBy(appleComparator));另一个常见的返回单个值的归约操作是对流中对象的一个数值字段求和,或者求平均数,这种操作被称为汇总操作。让我们来看看如何使用收集器来表达汇总操作。
Collectors.summingInt 汇总求和;
Collectors.averagingInt 汇总求平均值;
Collectors.summarizingInt 汇总所有信息包括数量、求和、平均值、最小值、最大值;
//汇总(数量、求和、平均值、最小值、最大值) IntSummaryStatistics intSummaryStatistics = appleList.stream().collect(Collectors.summarizingInt(Apple::getWeight)); System.out.println(intSummaryStatistics); //求和 int sumValue = appleList.stream().mapToInt(Apple::getWeight).sum(); System.out.println(sumValue); //求平均值 double avgValue = appleList.stream().collect(Collectors.averagingInt(Apple::getWeight)); System.out.println(avgValue);结果如下:
//汇总(数量、求和、平均值、最小值、最大值) IntSummaryStatistics{count=3, sum=340, min=60, average=113.333333, max=200} //求和 340 //求平均值 113.33333333333333同样,相应的 summarizingLong 和 summarizingDouble 工厂方法有相关的LongSummaryStatistics 和 DoubleSummaryStatistics 类型,适用于收集的属性是原始类型 long 或 double 的情况。
Collectors 类还提供了字符串操作的收集器,用于从流中生成一个字符串。主要方法是:
joining():从流中拼接一个字符串;joining(CharSequence):从流中拼接一个字符串,并指定分隔符;joining(CharSequence,CharSequence,CharSequence):从流中拼接一个字符串,并指定分隔符、前缀、后缀;把所有苹果的颜色以逗号分隔连成一个字符串
//joining String colors = appleList.stream().map(Apple::getColor).collect(Collectors.joining(",")); System.out.println(colors);结果如下:
green,yellow,red上面讨论的所有收集器,都是一个可以用 reducing 工厂方法定义的归约过程的特殊情况而已。Collectors.reducing 工厂方法是所有这些特殊情况的一般化,它完全可以实现上述方法的功能,它需要三个参数:
第一个参数是归约操作的起始值,也是流中没有元素时的返回值,所以很显然对于数值和而言0是一个合适的值。第二个参数是一个 Function,就是具体的取值函数。第三个参数是一个 BinaryOperator,将两个项目累积成一个同类型的值。1、计算汇总int值(同 summingInt)
appleList.stream().collect(Collectors.reducing(0, Apple::getWeight, (a1, a2) -> a1 + a2)); 等价 appleList.stream().map(Apple::getWeight).reduce(0, (a1, a2) -> a1 + a2); 等价 appleList.stream().map(Apple::getWeight).reduce(0, Integer::sum);2、计算最大值(同 maxBy)
appleList.stream().collect(Collectors.reducing((a1, a2) -> a1.getWeight() > a2.getWeight() ? a1 : a2)); 等价 appleList.stream().map(Apple::getWeight).reduce((a1, a2) -> a1 > a2 ? a1 : a2);3、连接字符串(同 join)
appleList.stream().collect(Collectors.reducing("",Apple::getColor,(a1,a2)->a1.concat(a2))); 等价 appleList.stream().map(Apple::getColor).reduce("", String::concat);用Collectors.groupingBy工厂方法返回的收集器可以实现分组任务,分组操作的结果是一个Map,把分组函数返回的值作为映射的键,把流中 所有具有这个分类值的项目的列表作为对应的映射值。
1、按照苹果颜色分组
Map<String,List<Apple>> map= appleList.stream().collect(Collectors.groupingBy(Apple::getColor)); System.out.println(map);结果如下:
green,yellow,red {red=[Apple{weight=200, color='red', price=6.9, productionPlace='上海'}], green=[Apple{weight=60, color='green', price=12.5, productionPlace='北京'}], yellow=[Apple{weight=80, color='yellow', price=10.1, productionPlace='天津'}]}2、按照苹果颜色分组,再按产地分组
Map<String,Map<String,List<Apple>>> map= appleList.stream().collect(Collectors.groupingBy(Apple::getColor,Collectors.groupingBy(Apple::getProductionPlace))); System.out.println(map);结果如下:
{red={上海=[Apple{weight=200, color='red', price=6.9, productionPlace='上海'}]}, green={北京=[Apple{weight=60, color='green', price=12.5, productionPlace='北京'}]}, yellow={天津=[Apple{weight=80, color='yellow', price=10.1, productionPlace='天津'}]}}3、统计每种颜色苹果的数量
Map<String,Long> map1= appleList.stream().collect(Collectors.groupingBy(Apple::getColor,Collectors.counting())); System.out.println(map1);结果如下:
{red=1, green=1, yellow=1}分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。分区函数返回一个布尔值,这意味着得到的分组 Map 的键类型是 Boolean,于是它最多可以分为两组true和false。分区的好处在于保留了分区函数返回true或false的两套流元素列表。
Map<Boolean, List<Apple>> mapPartion = appleList.stream().collect(Collectors.partitioningBy(a -> a.getPrice() > 0)); System.out.println(mapPartion); Map<Boolean, Map<String, List<Apple>>> mapPartion2 = appleList.stream().collect(Collectors.partitioningBy(a -> a.getPrice() > 0, Collectors.groupingBy(Apple::getProductionPlace))); System.out.println(mapPartion2);结果如下:
{false=[], true=[Apple{weight=60, color='green', price=12.5, productionPlace='北京'}, Apple{weight=80, color='yellow', price=10.1, productionPlace='天津'}, Apple{weight=200, color='red', price=6.9, productionPlace='上海'}]} {false={}, true={上海=[Apple{weight=200, color='red', price=6.9, productionPlace='上海'}], 天津=[Apple{weight=80, color='yellow', price=10.1, productionPlace='天津'}], 北京=[Apple{weight=60, color='green', price=12.5, productionPlace='北京'}]}}