前文《Java 8 | Stream 简介与常用方法》介绍了 collect 方法由 Stream 里的值生成一个列表,实际上这里就是使用了收集器来返回一个 List。收集器是一种通用的、从流中生成复杂值的结构。collect 方法使用收集器来完成复杂的操作,方法定义如下。
<R, A> R collect(Collector<? super T, A, R> collector);
Java 8 标准库提供了一些常用的收集器,由 java.util.stream.Collectors 静态类提供。
先准备数据,定义了一个 Student 类描述学生,包括 age、name、man 等域,实现了 Comparable 接口用于比较年龄大小,覆盖了 toString 方法。本文的实例都基于此数据类型。
public class Student implements Comparable {
long age;//年龄 String name;//姓名 boolean man;//性别男
public Student(long age, String name, boolean isMan) {
this.age = age;
this.name = name;
this.man = isMan;
}
public long getAge() {
return age;
}
public String getName() {
return name;
}
public boolean isMan() {
return man;
}
@Override
public String toString() {
return "(" + age + ", " + name + ", "
+ (man ? "man":"woman") + ")";
}
@Override
public int compareTo(Object o) {
Student student = (Student) o;
return (int)(age - student.getAge());
}
}
初始化数据,创建 4 个学生对象。
public List<Student> students = Arrays.asList(
new Student(18, "LiLei",true)
,new Student(19, "Jack",true)
,new Student(16,"Mei",false)
,new Student(17,"Andy",false));
转换成其他集合
Collectors 提供了用于生成 List、Set、Collection、Map 等多种数据类型的收集器,主要如下:
toList :从流中生成 List,List 的类型由 Stream 自动判断;
toSet :从流中生成 Set,Set 的类型由 Stream 自动判断;
toMap:从流中生成 Map,key-value 的类型需要自己指定,有两个重载,一个使用默认的 HashMap,一个可以自己指定 Map 类型;
toCollection:从流中生成 Collection,Collection 的具体类型自己指定。
比如可以使用 toMap 方法从学生列表中提取出名字、年龄的 HashMap,可以使用 toCollection 方法从学生列表中提取出按 TreeSet 类型存储的学生集合(按年龄排序生成树)。
public void convertToCollection(){
Map<String, Long> map = students.stream()
.collect(Collectors.toMap(Student::getName, Student::getAge));
System.out.println(map);
TreeSet<Student> set = students.stream()
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(set);
}
执行结果如下,第一行打印了名字、年龄的 Map 数据,第二行打印了按年龄从小到大的排序。
{Mei=16, Jack=19, LiLei=18, Andy=17}
[(16, Mei, woman), (17, Andy, woman), (18, LiLei, man), (19, Jack, man)]
数值计算
Collectors 类提供了一些数值计算的方法,如:
averagingDouble :计算 double 类型数值的平均值;
averagingInt:计算 int 类型数值的平均值;
averagingLong:计算 long 类型数值的平均值;
maxBy:计算最大值;
minBy:计算最小值;
summingDouble :计算 double 类型数值的和;
summingInt:计算 int 类型数值的和;
summingLong:计算 long 类型数值的和;
counting:统计元素的个数。
下面的代码演示了使用 maxBy 从学生列表中找到年龄最大的学生,然后计算所有学生年龄的平均值、和。
public void convertToValue(){
Student student = students.stream()
.collect(Collectors.maxBy(Comparator.comparing(Student::getAge)))
.get();
System.out.println(student);
double age = students.stream()
.collect(Collectors.averagingLong(Student::getAge));
System.out.println(age);
long sum = students.stream()
.collect(Collectors.summingLong(Student::getAge));
System.out.println(sum);
}
执行结果如下,年龄最大学生为 Jack,所有学生年龄平均值为 17.5,年龄总和为 70。
(19, Jack, man)
17.5
70
数据分块
Collectors 类提供了一种方法可以把集合分成两部分,这个方法为 partitioningBy。该方法接收一个 Predicate 对象用于判断一个元素应该属于哪个部分,然后根据返回的 true 和 false,把元素放入 Map 中。Map 的 key 只有 true 和 false 两个,value 是元素列表。如下的代码演示了根据性别把学生分为两部分。
public void partition(){
Map<Boolean, List<Student>> block = students.stream()
.collect(
Collectors.partitioningBy(Student::isMan)
);
System.out.println(block);
}
执行结果如下,Map 中 false 对应的列表是女生,true 对应的列表是男生。
{false=[(16, Mei, woman), (17, Andy, woman)], true=[(18, LiLei, man), (19, Jack, man)]}
数据分组
把数据分为两部分并不能解决所有问题,Collectors 类提供了 groupingBy 方法,来提供一个可以按需分组的收集器。groupingBy 方法接收一个 Function 对象来对数据进行分组,分组之后的数据也是存储在 Map 中。如下的代码演示了根据学生的年龄对学生进行分组。
public void group(){
Map<Long, List<Student>> group = students.stream()
.collect(Collectors.groupingBy(Student::getAge));
System.out.println(group);
}
执行结果如下,返回的 Map 的 key 是年龄,value 是学生列表。
{16=[(16, Mei, woman)], 17=[(17, Andy, woman)], 18=[(18, LiLei, man)], 19=[(19, Jack, man)]}
字符串
Collectors 类还提供了字符串操作的收集器,用于从流中生成一个字符串。主要方法是:
joining():从流中拼接一个字符串;
joining(CharSequence):从流中拼接一个字符串,并指定分隔符;
joining(CharSequence,CharSequence,CharSequence):从流中拼接一个字符串,并指定分隔符、前缀、后缀;
如下的代码演示了从学生列表中提取出所有学生的名字,并组成一个字符串,使用逗号分隔。
public void string(){
String names = students.stream()
.map(Student::getName)
.collect(Collectors.joining(","));
System.out.println(names);
}
执行结果如下。
LiLei,Jack,Mei,Andy
组合收集器
多个收集器也可以组合使用,实现非常复杂的功能。mapping、groupingBy、partitioningBy 方法的第二个参数可以传入一个下游收集器,用于在当前收集器工作完成后调用下一个收集器。
如下的代码在前面按照性别对学生分类的基础上,传入了一个下游收集器,下游收集器是一个 maxBy 收集器。收集器的工作分为两步,第一步按性别分类,第二部找出每个学生列表年龄最大的学生。
public void combination(){
Map<Boolean, Optional<Student>> group = students.stream()
.collect(
Collectors.groupingBy( Student::isMan
,Collectors.maxBy(
Comparator.comparing(Student::getAge)
)
)
);
System.out.println(group);
}
执行结果如下,女生中年龄最大的是 Andy,男生中年龄最大的是 Jack。
{false=Optional[(17, Andy, woman)], true=Optional[(19, Jack, man)]}
每周 3 篇学习笔记或技术总结,内容涉及 Java 进阶、虚拟机、MySQL、NoSQL、分布式计算、开源框架等多个领域。关注作者或微信公众号 后端开发那点事儿 第一时间获取最新内容。