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 对象做了二级分组。