文章目录
这一张我们主要讲用流来收集数据,函数式的编程相对于指令式编程的一个优势:你只需指出希望的结果做什么,而不用操心执行的步骤怎么做。
这一节所讲的方法都是基于收集collect里面参数Collectors里面的静态方法静态方法。
小插曲:import static java.util.stream.Collectors.*;可以把所有静态方法引入
一、收集器简介
收集器非常有用,因为它可以简洁而又灵活地定义collect用来生成结果集合的标准。更具体说,对流调用collect方法将对流中的元素发一个归约操作。
本章剩下部分,主要探讨预定义收集器功能,也就是那些可以从Collector类提供的工厂方法创建收集器。主要提供三大功能:
- 将流元素归约和汇总一个值
- 元素分组
- 元素分区
二、归约和汇总
利用counting
public static void testCounting() {
Long collect = menu.stream().collect(counting());
}
1、查找流中最大值和最小值Collectors.maxBy和,Collectors.minBy
maxBy参数接受一个Compartor比较结果。例:
public static void testMaxby() {
Comparator<Dish> dishComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> collect = menu.stream().collect(maxBy(dishComparator));
}
2、汇总
Collectors类专门为汇总提供了一个工厂方法:Collectors.summingInt。接受一个对象映射为求和所需int函数。例:
public static void testSummingInt() {
Integer collect = menu.stream().collect(summingInt(Dish::getCalories));
System.out.println(collect);
}
Collectors.summingLong和Collectors.summingDouble方法用法完全一样可以用于求和long和double,求平球输方法averagingLong、averagingDouble、averagingInt。
summarizingInt、summarizingLong、summarizingDouble方法可以数出元素个数、平均数、总和、最大值和最小值。例:
IntSummaryStatistics menuStatistics =
menu.stream().collect(summarizingInt(Dish::getCalories));
3、连接字符串
joining工厂方法返回的收集器会把中每一个对象应用toString()方法得到的所有字符串
连接成一个字符串。例:把所有的菜肴名称连接起来
public static void testJoining() {
String collect = menu.stream().map(Dish::getName).collect(joining());
System.out.println(collect);
}
重载方法可以添加用什么分隔符
public static void testJoining() {
String collect = menu.stream().map(Dish::getName).collect(joining(","));
System.out.println(collect);
}
4、广义归约汇总
前面已经讨论过的收集器,可以用一个reduing工厂方法定义归约过程的特殊情况。例:
public static void testCollectReduing() {
Integer collect = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
}
它需要三个参数:
第一个参数:归约起始位置
第二个参数:转换成表示所函热量的int
第三个参数:是一个BInaryOperator,将两个项目累计为同一个值。例如对int求和。
三、分组
java8分组类似于常见数据库分组,我们java8提供了Collectors.groupingBy工厂方法返回的收集器就可以轻松的完成分组。例:
按照菜单类型分组
Map<Dish.Type, List<Dish>> dishesByType =
menu.stream().collect(groupingBy(Dish::getType));
1、多级分组
实现多级分组,可以使用一个由双参数版本的Collectors.groupingBy工厂方法创建收集器,除了普通的分类函数外,还可以接受collector类型的第二个参数。进行二级分组可以把内层的groupingby传递给外层。
例:
public static void testCollecGroupBying() {
Map<Type, Map<CaloricLevel, List<Dish>>> collect = menu.stream().collect(groupingBy(Dish::getType, groupingBy(dish ->
{
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NOMAL;
else return CaloricLevel.FAT;
}
)));
System.out.println(collect);
}
n级分组就会得到一个n级树形结构的n级Map
2、按子组收集数据
传递给第一个groupingBy的第二个收集器可以是任何类型,而不一定是一个groupingBy。例:
Map<Dish.Type, Long> typesCount = menu.stream().collect(
groupingBy(Dish::getType, counting()));
注意:普通单参数groupingBy单参数其实就是groupbingBy(f,toList)的简写
在举一个例子加深印象
Map<Type, Optional<Dish>> collect1 = menu.stream()
.collect(groupingBy(Dish::getType,
maxBy(Comparator.comparingInt(Dish::getCalories))));
3、将收集器结果转换为另一种类型
收集器转换成另一种类型可以用:Collectors.collectingAndThen
例:
public static void testCollectAndThen() {
Map<Type, Dish> collect = menu.stream().collect(groupingBy(Dish::getType,
collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));
}
}
collectingAndThen工厂发方法接受两个参数:1、转换收集器(Collector);2、转换函数(Function)。返回值是另一个收集器。
4、分区partitioningBy
分区的好处在于保留分区函数返回true或false的两套流元素列表。partitioningBy工厂方法有一个重载方法,第二个参数可以传递收集器。
public static void testPartitioningBy() {
Map<Boolean, Map<Type, List<Dish>>> collect = menu.stream().
collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
}
总结:Collectors类的静态工厂方法