首页 > 编程笔记 > Java笔记 阅读:25

java Collectors.groupingBy()的用法(附带实例)

Java Collectors 类提供的 groupingBy() 方法是用来对数据进行分组的方法,方法参数是一个 Function 接口对象,收集器会按照指定的函数规则对数据进行分组。

数据分组就是将流中元素按照指定的条件分开进行保存,类似 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=运营部
本例有两个难点:
介绍完一级分组后,再介绍复杂的多级分组。

一级分组是按照一个条件进行分组的,那么多级分组就是按照多个条件进行分组的。还是用学校举例,学校有 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 对象做了二级分组。

相关文章