首页 > 解决方案 > 如何在 Java Stream 中使用 MaxBy 进行分组和聚合

问题描述

我有一个Employee对象列表如下。

public class Employee {
    int eno;
    String name;
    String dept;
    int  salary;
   ......
}

要求是获取每个部门获得最高薪水的员工姓名列表。部门中可以有不止一名员工具有相同的最高薪水。例如,销售团队可能有 2 名员工,比如 Alex 和 John,他们的薪水最高。

List<Employee> eList = new ArrayList<>();
eList .add(new Employee(101, "ALEX","SALES",7000));
eList.add(new Employee(102, "KATHY","ADMIN",3000));
eList.add(new Employee(103, "JOHN","SALES",7000));
eList.add(new Employee(104, "KONG","IT",5000));
eList.add(new Employee(105, "MIKE","SALES",4000));

预期的输出是 -> Map(DEPT, List Of Names with max payment)

使用下面的代码,我只能得到一个 Employee 对象作为输出,即使有多个员工满足条件。

Map<String, Employee> maxSalEmpNameByDept = eList.stream().collect(groupingBy(Employee::getDept, 
                                                        collectingAndThen(maxBy(Comparator.comparing(Employee::getSalary)), Optional::get)));  

这是因为 MaxBy 方法在找到满足给定条件的第一个项目时停止。

我的问题是:

  1. 我们如何获得Map <String, List<Employee>>每个部门的最高薪员工 ( ) 的列表?
  2. 如何仅检索Map <String, List<String>>每个部门中薪水最高的员工姓名列表( )?

标签: javajava-8java-stream

解决方案


这是一种不使用显式Comparator或第三方库的方法。我填写了您的 Employee 类并添加了一些额外的员工。

提供的数据

List<Employee> eList = new ArrayList<>();
eList.add(new Employee(101, "ALEX", "SALES", 7000));
eList.add(new Employee(102, "KATHY", "ADMIN", 3000));
eList.add(new Employee(103, "JOHN", "SALES", 7000));
eList.add(new Employee(104, "KONG", "IT", 5000));
eList.add(new Employee(106, "MIKE", "SALES", 4000));
eList.add(new Employee(107, "BOB", "IT", 4000));
eList.add(new Employee(108, "SARAH", "ADMIN", 2000));
eList.add(new Employee(109, "RALPH", "SALES", 4000));
eList.add(new Employee(110, "JUNE", "ADMIN", 3000));

以下是每个部门的最高工资。

  • 首先,按部门对第一张地图进行分组。
  • 现在创建另一个地图并按员工的薪水对员工进行分组。工资是这张地图的关键。该值将是员工列表。
  • 由于内部映射是 a Treemap,因此值自然从低到高排序,因此最后一个条目的薪水最高。
  • 只需检索每个部门的最后一个条目,您就会拥有最高薪的员工。
Map<String, List<Employee>> highestSalaries = eList.stream()
        .collect(Collectors.groupingBy(Employee::getDept,
                Collectors.groupingBy(Employee::getSalary,
                        TreeMap::new, Collectors.toList())))
        .entrySet().stream()
        .collect(Collectors.toMap(e -> e.getKey(),
                e -> e.getValue().lastEntry().getValue()));

highestSalaries.entrySet().forEach(System.out::println);

印刷

ADMIN=[{102,  KATHY,  ADMIN,  3000}, {110,  JUNE,  ADMIN,  3000}]
IT=[{104,  KONG,  IT,  5000}]
SALES=[{101,  ALEX,  SALES,  7000}, {103,  JOHN,  SALES,  7000}]

要仅获取名称,请使用刚刚创建的地图

  • 流式传输值(这是员工列表)
  • 然后通过 flatMap 将员工列表放在一个Employee流中
  • 请记住,此时这些是每个部门的最高薪水,因此只需按部门对员工进行分组并在列表中收集他们的姓名。
Map<String, List<String>> highestSalariesNames = 
        highestSalaries.values().stream()
                .flatMap(List::stream)
                .collect(Collectors.groupingBy(
                        Employee::getDept,
                        Collectors.mapping(Employee::getName,
                                Collectors.toList())));

highestSalariesNames.entrySet().forEach(System.out::println);

印刷

IT=[KONG]
ADMIN=[KATHY, JUNE]
SALES=[ALEX, JOHN]

我用构造函数、getter 和 toString 修改了你的类。

    public static class Employee {
        private int eno;
        private String name;
        private String dept;
        private int salary;
        
        public Employee(int eno, String name, String dept,
                int salary) {
            this.eno = eno;
            this.name = name;
            this.dept = dept;
            this.salary = salary;
        }
        
        public int getEno() {
            return eno;
        }
        
        public String getName() {
            return name;
        }
        
        public String getDept() {
            return dept;
        }
        
        public int getSalary() {
            return salary;
        }
        
        @Override
        public String toString() {
            return String.format("{%s,  %s,  %s,  %s}", eno, name,
                    dept, salary);
        }
    }
    
}

推荐阅读