java Collectors.groupingBy()的用法(附带实例)
Java Collectors 类提供的 groupingBy() 方法是用来对数据进行分组的方法,方法参数是一个 Function 接口对象,收集器会按照指定的函数规则对数据进行分组。
数据分组就是将流中元素按照指定的条件分开进行保存,类似 SQL 语言中的“GROUP BY”关键字。分组之后的数据会被按照不同的标签分别保存成一个集合,然后按照“键-值”关系封装在 Map 对象中。
数据分组有一级分组和多级分组两种场景,首先先来介绍一级分组。
一级分组,就是将所有数据按照一个条件进行归类。例如,学校有 100 名学生,这些学生分布在 3 个年级中。学生被按照年级分成了 3 组,然后就不再细分了,这就属于一级分组。
【实例 1】将所有员工按照部门进行分组。
运行结果如下:
介绍完一级分组后,再介绍复杂的多级分组。
一级分组是按照一个条件进行分组的,那么多级分组就是按照多个条件进行分组的。还是用学校举例,学校有 100 名学生,这些学生分布在 3 个年级中,这是一级分组,但每个年级还有若干个班级,学生被分到不同年级之后又被分到不同的班里,这就是二级分组。
如果学生再被按男女分组,就变成了三级分组。元素按照两个以上的条件进行分组,就是多级分组。
Collectors 类提供的 groupingBy() 方法还提供了一个重载形式:
【实例 2】将所有员工先按照部门分组,再按照性别分组。
运行结果如下:
1) 实例中两个 groupingBy() 方法的参数不一样,一个是 groupingBy(性别分组规则),另一个是 groupingBy(部门分组规则, groupingBy(性别分组规则) )。
2) 在获得的 Map 对象中,还嵌套了 Map 对象,它的结构是这样的:
数据分组就是将流中元素按照指定的条件分开进行保存,类似 SQL 语言中的“GROUP BY”关键字。分组之后的数据会被按照不同的标签分别保存成一个集合,然后按照“键-值”关系封装在 Map 对象中。
数据分组有一级分组和多级分组两种场景,首先先来介绍一级分组。
一级分组,就是将所有数据按照一个条件进行归类。例如,学校有 100 名学生,这些学生分布在 3 个年级中。学生被按照年级分成了 3 组,然后就不再细分了,这就属于一级分组。
【实例 1】将所有员工按照部门进行分组。
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.ArrayList;
public class Employee {
private String name; // 姓名
private int age; // 年龄
private double salary; // 工资
private String sex; // 性别
private String dept; // 部门
// 构造方法
public Employee(String name, int age, double salary, String sex, String dept) {
this.name = name;
this.age = age;
this.salary = salary;
this.sex = sex;
this.dept = dept;
}
// 重写 toString()方法,方便输出员工信息
public String toString() {
return "name=" + name + ", age=" + age + ", salary=" + salary + ", sex=" + sex + ", dept=" + dept;
}
// 以下是员工属性的 getter 方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getSalary() {
return salary;
}
public String getSex() {
return sex;
}
public String getDept() {
return dept;
}
static List<Employee> getEmpList() { // 提供数据初始化方法
List<Employee> list = new ArrayList<Employee>();
list.add(new Employee("老张", 40, 9000, "男", "运营部"));
list.add(new Employee("小刘", 24, 5000, "女", "开发部"));
list.add(new Employee("大刚", 32, 7500, "男", "销售部"));
list.add(new Employee("翠花", 28, 5500, "女", "销售部"));
list.add(new Employee("小马", 21, 3000, "男", "开发部"));
list.add(new Employee("老王", 35, 6000, "女", "人事部"));
list.add(new Employee("小王", 21, 3000, "女", "人事部"));
return list;
}
}
public class GroupingDemo1 {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 分组规则方法,按照员工部门进行分组
Function<Employee, String> f = Employee::getDept;
// 按照部门分成若干个 List 集合,在集合中保存员工对象,返回给 Map 对象
Map<String, List<Employee>> map = stream.collect(Collectors.groupingBy(f));
Set<String> keySet = map.keySet(); // 获取 Map 对象中有部门名称
for (String deptName : keySet) { // 遍历部门名称集合
// 输出部门名称
System.out.println("[ " + deptName + " ] 部门的员工列表如下:");
List<Employee> deptList = map.get(deptName); // 获取部门名称对应的员工集合
for (Employee emp : deptList) { // 遍历员工集合
System.out.println(" " + emp); // 输出员工信息
}
}
}
}
程序中,定义了 Employee 员工类,在获取员工集合后,创建 Function 接口对象 f,f 引用 Employee 类的 getDept() 方法获取部门名称。然后,流的收集器类按照f的规则进行分组,Stream 对象将分组结果赋值给一个 Map 对象,Map 对象将会以“key:部门,value:员工List”的方式保存数据。运行结果如下:
【销售部】 部门的员工列表如下:
name=大刚, age=32, salary=7500.0, sex=男, dept=销售部
name=翠花, age=28, salary=5500.0, sex=女, dept=销售部
【人事部】 部门的员工列表如下:
name=老王, age=35, salary=6000.0, sex=女, dept=人事部
name=小王, age=21, salary=3000.0, sex=女, dept=人事部
【开发部】 部门的员工列表如下:
name=小刘, age=24, salary=5000.0, sex=女, dept=开发部
name=小马, age=21, salary=3000.0, sex=男, dept=开发部
【运营部】 部门的员工列表如下:
name=老张, age=40, salary=9000.0, sex=男, dept=运营部
本例有两个难点:
- 分组规则是一个函数,这个函数是由 Collectors 收集器类调用的,而不是 Stream 流对象。
- Map<K,List<T>> 有两个泛型,第一个泛型是组的类型,第二个泛型是组内的元素集合类型。本例是将所有员工按照部门名称进行分组的,因此 K 的类型是 String 类型;部门内的元素是员工集合,因此 List 集合泛型 T 的类型就应该是 Employee 类型。
介绍完一级分组后,再介绍复杂的多级分组。
一级分组是按照一个条件进行分组的,那么多级分组就是按照多个条件进行分组的。还是用学校举例,学校有 100 名学生,这些学生分布在 3 个年级中,这是一级分组,但每个年级还有若干个班级,学生被分到不同年级之后又被分到不同的班里,这就是二级分组。
如果学生再被按男女分组,就变成了三级分组。元素按照两个以上的条件进行分组,就是多级分组。
Collectors 类提供的 groupingBy() 方法还提供了一个重载形式:
groupingBy(Function<? super T,? extends K> classifier, Collector<? super T,A,D> downstream)这个重载方法的第二个参数也是一个收集器,当分组前数据包含其他分组的结果,这就构成了多级分组功能。
【实例 2】将所有员工先按照部门分组,再按照性别分组。
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class GroupingDemo2 {
public static void main(String[] args) {
List<Employee> list = Employee.getEmpList(); // 获取公共类的测试数据
Stream<Employee> stream = list.stream(); // 获取集合流对象
// 一级分组规则方法,按照员工部门进行分组
Function<Employee, String> deptFunc = Employee::getDept;
// 二级分组规则方法,按照员工性别进行分组
Function<Employee, String> sexFunc = Employee::getSex;
// 将流中的数据进行二级分组,先对员工部门进行分组,再对员工性别进行分组
Map<String, Map<String, List<Employee>>> map = stream
.collect(Collectors.groupingBy(deptFunc, Collectors.groupingBy(sexFunc)));
// 获取 Map 对象中的一级分组键集合,也就是部门名称集合
Set<String> deptSet = map.keySet();
for (String deptName : deptSet) { // 遍历部门名称集合
System.out.println("[ " + deptName + " ] 部门的员工列表如下:");
// 获取部门对应的二级分组的 Map 对象
Map<String, List<Employee>> sexMap = map.get(deptName);
// 获取二级分组的键集合,也就是性别集合
Set<String> sexSet = sexMap.keySet();
for (String sexName : sexSet) { // 遍历部门性别集合
// 获取性别对应的员工集合
List<Employee> empList = sexMap.get(sexName);
System.out.println(" [ " + sexName + " ] 员工:");
for (Employee emp : empList) { // 遍历员工集合
System.out.println(" " + emp); // 输出对应员工信息
}
}
}
}
}
在一级分组实例的基础上,首先创建 Function 接口对象 deptFunc 以用于引用获取部门的方法,再创建 Function 接口对象 sexFunc 以用于引用获取性别的方法(这两个对象将作为一级分组和二级分组的函数规则),最后将按照性别分组的 Collectors.groupingBy(sexFunc) 方法作为另一个 groupingBy() 方法的参数,按照部门进行分组,这样就实现了二级分组。运行结果如下:
【销售部】 部门的员工列表如下:
【女】 员工:
name=翠花, age=28, salary=5500.0, sex=女, dept=销售部
【男】 员工:
name=大刚, age=32, salary=7500.0, sex=男, dept=销售部
【人事部】 部门的员工列表如下:
【女】 员工:
name=老王, age=35, salary=6000.0, sex=女, dept=人事部
name=小王, age=21, salary=3000.0, sex=女, dept=人事部
【开发部】 部门的员工列表如下:
【女】 员工:
name=小刘, age=24, salary=5000.0, sex=女, dept=开发部
【男】 员工:
name=小马, age=21, salary=3000.0, sex=男, dept=开发部
【运营部】 部门的员工列表如下:
【男】 员工:
name=老张, age=40, salary=9000.0, sex=男, dept=运营部
这个结果先按照部门进行了分组,然后又对部门中的男女进行了二级分组。这个实例也有两个难点:1) 实例中两个 groupingBy() 方法的参数不一样,一个是 groupingBy(性别分组规则),另一个是 groupingBy(部门分组规则, groupingBy(性别分组规则) )。
2) 在获得的 Map 对象中,还嵌套了 Map 对象,它的结构是这样的:
Map<部门, Map<性别, List<员工>>>从左数,第一个 Map 对象做了一级分组,第二个 Map 对象做了二级分组。
ICP备案:
公安联网备案: