Java Lambda从入门到精通十八 收集器Collectors

收集器Collectors是对Stream API的一个补充,就好像我们的集合类有一个Collections工具类一样,Collectors内部实现了大量的静态方法,方便我们进行流的规约、求和、求最、分组等等操作。

本节主要讲解的内容:

收集数据

汇总和规约

求值工厂

字符串拼接

分组

分区

我们来看代码,先构建测试数据:

List<Student> list = new ArrayList<>();
String[] names = {"小明","小华","小志","小东","小李","小张","小王","小周","小吴","小郑"};
for(int i=0; i<10; i++) {
	Student student = Student.builder()
			.name(names[i])
			.age(12+i/5)
			.classId(i%3+1)
			.no(i+1)
			.math(85d+i).build();
	list.add(student);
}
//初始化数据
System.out.println("======初始化数据:======");
list.forEach(System.out::println);

输出结果:

======初始化数据:======
Student(classId=1, no=1, name=小明, age=12, math=85.0, chinese=null, english=null, score=null)
Student(classId=2, no=2, name=小华, age=12, math=86.0, chinese=null, english=null, score=null)
Student(classId=3, no=3, name=小志, age=12, math=87.0, chinese=null, english=null, score=null)
Student(classId=1, no=4, name=小东, age=12, math=88.0, chinese=null, english=null, score=null)
Student(classId=2, no=5, name=小李, age=12, math=89.0, chinese=null, english=null, score=null)
Student(classId=3, no=6, name=小张, age=13, math=90.0, chinese=null, english=null, score=null)
Student(classId=1, no=7, name=小王, age=13, math=91.0, chinese=null, english=null, score=null)
Student(classId=2, no=8, name=小周, age=13, math=92.0, chinese=null, english=null, score=null)
Student(classId=3, no=9, name=小吴, age=13, math=93.0, chinese=null, english=null, score=null)
Student(classId=1, no=10, name=小郑, age=13, math=94.0, chinese=null, english=null, score=null)

1、收集数据,例如toList()方法,我们之前已经用了很多次,作用就是将输入元素累加到一个新的List中并返回,这个累加并不是数值相加,而是积累的意思,相当于就是list.add(x)

//收集数据
List<Double> toList = list.stream().map(Student::getMath).collect(Collectors.toList());
System.out.println("收集学生数学成绩存入List中:"+toList);

输出结果:

收集学生数学成绩存入List中:[85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0]

2、汇总和规约,收集器中的汇总规约和Stream中的汇总规约作用是一样的,我们来看4个例子

Long counting = list.stream().collect(Collectors.counting());
System.out.println("统计数据总数:"+counting);
long count = list.stream().count();
System.out.println("统计数据总数:"+count);

Optional<Student> max = list.stream().max(Comparator.comparing(Student::getMath));
System.out.println("数学最高分学生:"+max.get());
Optional<Student> collect = list.stream().collect(maxBy(Comparator.comparing(Student::getMath)));
System.out.println("数学最高分学生:"+collect.get());


Double summingDouble = list.stream().collect(Collectors.summingDouble(Student::getMath));
System.out.println("所有学生数学分数加和:"+summingDouble);

Double averagingDouble = list.stream().collect(Collectors.averagingDouble(Student::getMath));
System.out.println("班级学生数学平均分:"+averagingDouble);

输出结果:

统计数据总数:10
统计数据总数:10
数学最高分学生:Student(classId=1, no=10, name=小郑, age=13, math=94.0, chinese=null, english=null, score=null)
数学最高分学生:Student(classId=1, no=10, name=小郑, age=13, math=94.0, chinese=null, english=null, score=null)
所有学生数学分数加和:895.0
班级学生数学平均分:89.5

3、求值工厂,通过求值工厂方法,能够帮我们一次性计算出:数量、求和、最小值、平均值、最大值

DoubleSummaryStatistics summarizingDouble = list.stream().collect(Collectors.summarizingDouble(Student::getMath));
System.out.println("求值工厂自动计算数量、求和、最小值、平均值、最大值:"+summarizingDouble);

输出结果:

求值工厂自动计算数量、求和、最小值、平均值、最大值:DoubleSummaryStatistics{
count=10, 
sum=895.000000, 
min=85.000000, 
average=89.500000, 
max=94.000000
}

4、字符串值拼接,joining方法支持按照指定分隔符进行字符串拼接,其内部使用了StringJoiner来进行字符串拼接,StringJoiner是jdk提供的高效字符串拼接类

//joining-拼接
String joining = list.stream().map(Student::getName).collect(Collectors.joining(","));
System.out.println("拼接学生姓名:"+joining);

输出结果:

拼接学生姓名:小明,小华,小志,小东,小李,小张,小王,小周,小吴,小郑

5、分组,和数据库中的group by是相似的功能,这个功能是Stream中没有直接提供的功能,所以收集器类为我们提供了更丰富、更方便的API

//分组
Map<Integer, List<Student>> groupBy = list.stream().collect(Collectors.groupingBy(Student::getClassId));
groupBy.forEach((x,y)->{
	System.out.println("班级:"+x+"--------------");
	y.forEach(System.out::println);
});

//分组计数
Map<Integer, Long> collect1 = list.stream().collect(Collectors.groupingBy(Student::getClassId, Collectors.counting()));
System.out.println("按照班级统计各班人数:"+ collect1);

//按班级ID分组计算数学最高分学生
Map<Integer, Optional<Student>> collect2 = list.stream().collect(Collectors.groupingBy(Student::getClassId, Collectors.maxBy(Comparator.comparingDouble(Student::getMath))));
collect2.forEach((x,y)->{
	System.out.println("班级ID:"+x.intValue()+",最高分学生:"+y.get().getName()+",数学最高分:"+y.get().getMath());
});

输出结果:

班级:1--------------
Student(classId=1, no=1, name=小明, age=12, math=85.0, chinese=null, english=null, score=null)
Student(classId=1, no=4, name=小东, age=12, math=88.0, chinese=null, english=null, score=null)
Student(classId=1, no=7, name=小王, age=13, math=91.0, chinese=null, english=null, score=null)
Student(classId=1, no=10, name=小郑, age=13, math=94.0, chinese=null, english=null, score=null)
班级:2--------------
Student(classId=2, no=2, name=小华, age=12, math=86.0, chinese=null, english=null, score=null)
Student(classId=2, no=5, name=小李, age=12, math=89.0, chinese=null, english=null, score=null)
Student(classId=2, no=8, name=小周, age=13, math=92.0, chinese=null, english=null, score=null)
班级:3--------------
Student(classId=3, no=3, name=小志, age=12, math=87.0, chinese=null, english=null, score=null)
Student(classId=3, no=6, name=小张, age=13, math=90.0, chinese=null, english=null, score=null)
Student(classId=3, no=9, name=小吴, age=13, math=93.0, chinese=null, english=null, score=null)
按照班级统计各班人数:{1=4, 2=3, 3=3}
班级ID:1,最高分学生:小郑,数学最高分:94.0
班级ID:2,最高分学生:小周,数学最高分:92.0
班级ID:3,最高分学生:小吴,数学最高分:93.0

6、分区,是特殊的分组,分组只能分为两组,即true和false

//分区,数学成绩大于等于90则为优秀
Map<Boolean, List<Student>> partitioningBy = list.stream().collect(partitioningBy(x -> x.getMath() >= 90));
String collect3 = partitioningBy.get(true).stream().map(Student::getName).collect(Collectors.joining(","));
System.out.println("数学成绩优秀的学生:"+collect3);
String collect4 = partitioningBy.get(false).stream().map(Student::getName).collect(Collectors.joining(","));
System.out.println("数学成绩非优秀的学生:"+collect4);

输出结果:

数学成绩优秀的学生:小张,小王,小周,小吴,小郑
数学成绩非优秀的学生:小明,小华,小志,小东,小李