文章目录
1、Collectors 收集器简介
收集器可以简洁而灵活地定义collect用来生成结果集合的标准。更具体地说,对流调用 collect 方法将对流中的元素触发一个归约操作(由Collector来参数化)。一般来说,Collector 会对元素应用一个转换函数(很多时候是不体现任何效果的恒等转换, 例如 toList ),并将结果累积在一个数据结构中,从而产生这一过程的最终输出。下面就来学习那些可以从Collectors 类提供的工厂方法(例如groupingBy)创建的收集器。
collect 方法使用收集器来完成复杂的操作,方法定义如下:
<R, A> R collect(Collector<? super T, A, R> collector);
Java 8 标准库提供了一些常用的收集器,由 java.util.stream.Collectors 静态类提供。
2、Collectors 收集器常用静态方法
工厂方法 | 返回类型 | 作用 |
---|---|---|
toList | List<T> | 把流中所有项目收集到一个 List |
toSet | Set<T> | 把流中所有项目收集到一个 Set,删除重复项 |
toCollection | Collection<T> | 把流中所有项目收集到给定的供应源创建的集合menuStream.collect(toCollection(), ArrayList::new) |
counting | Long | 计算流中元素的个数 |
sumInt | Integer | 对流中项目的一个整数属性求和 |
averagingInt | Double | 计算流中项目 Integer 属性的平均值 |
summarizingInt | IntSummaryStatistics | 收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值 |
joining | String | 连接对流中每个项目调用 toString 方法所生成的字符串collect(joining(", ")) |
maxBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty() |
minBy | Optional<T> | 一个包裹了流中按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty() |
reducing | 归约操作产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流 中的元素逐个结合,从而将流归约为单个值累加int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个收集器,对其结果应用转换函数int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size)) |
groupingBy | Map<K, List<T>> | 根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键 |
partitioningBy | Map<Boolean,List<T>> | 根据对流中每个项目应用谓词的结果来对项目进行分区 |
3、预定义收集器
为了方便收集器的使用,Java提供了 Collectors 类,其包含众多常用的收集器工厂方法。主要提供了三大功能:
- 将流元素归约和汇总为一个值
- 元素分组
- 元素分区
3.1、归约汇总
3.1.1、Collectors.counting()
计算苹果的数量,功能同 count()
appleList.stream().collect(Collectors.counting());
同
appleList.stream().count();
3.1.2、Collectors.minBy、Collectors.maxBy
假设我们要找出重量最大和最小的苹果,我们可以使用两个收集器, 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));
3.1.3、Collectors.summingInt、Collectors.summingLong、Collectors.summingDouble 等等
另一个常见的返回单个值的归约操作是对流中对象的一个数值字段求和,或者求平均数,这种操作被称为汇总操作。让我们来看看如何使用收集器来表达汇总操作。
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 的情况。
3.1.4、Collectors.joining
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
3.1.5、Collectors.reducing
上面讨论的所有收集器,都是一个可以用 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);
3.2、元素分组
用Collectors.groupingBy工厂方法返回的收集器可以实现分组任务,分组操作的结果是一个Map,把分组函数返回的值作为映射的键,把流中 所有具有这个分类值的项目的列表作为对应的映射值。
3.2.1、Collectors.groupingBy
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}
3.3、元素分区
分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。分区函数返回一个布尔值,这意味着得到的分组 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='北京'}]}}