【问题标题】:How to apply sorting and limiting after groupby using Java streams如何使用 Java 流在 groupby 之后应用排序和限制
【发布时间】:2020-07-01 15:14:10
【问题描述】:

我有以下员工数据列表,我需要根据员工部门对这些数据进行分组,然后我想找到每个部门中薪酬最高的 2 名员工。

public class Employee {

    private int id;
    private String name;
    private String dept;
    private int salary;

    //setters and getters
}

List<Employee> listOfEmp = new ArrayList<>();

listOfEmp.add(new Employee (1, "A", "IT",100));
listOfEmp.add(new Employee (2, "B", "IT",200));
listOfEmp.add(new Employee (3, "C", "SUPPORT",100));
listOfEmp.add(new Employee (4, "D", "SUPPORT",200));
listOfEmp.add(new Employee (5, "E", "SUPPORT",300));
listOfEmp.add(new Employee (6, "F", "SUPPORT",400));
listOfEmp.add(new Employee (7, "G", "IT",500));
listOfEmp.add(new Employee (8, "H", "IT",600));
listOfEmp.add(new Employee (9, "I", "IT",700));

以下是我写给按部门分组员工的查询

Map<String, List<String>> departmentWiseEmployees  = listOfEmp.stream().
        collect(Collectors.groupingBy(Employee::getDept, Collectors.mapping(Employee::getName, toList())));

我正在尝试使用以下方法使用以下方法查找每个部门中薪酬最高的 2 名员工。但它是

int limit = 2;
    Map<String, List<String>> groupByTeachers =
            listOfEmp.stream()
                    .collect(
                            Collectors.groupingBy(
                                    Employee::getDept,
                                    Collectors.collectingAndThen(
                                            Collectors.toList(),
                                            e -> e.stream().sorted().limit(limit).collect(toList() ) ) ) );

这里编译器抱怨为collectingAndThen提供的lambda参数出现以下错误,它说e的类型是List&lt;Employee&gt;,但它必须是List&lt;Object&gt;

有人可以帮我理解这里出了什么问题吗?

【问题讨论】:

  • empList 是如何定义的?
  • List listOfEmp = new ArrayList();

标签: java lambda java-8 java-stream


【解决方案1】:

这部分代码:

e -> e.stream().sorted().limit(limit).collect(Collectors.toList())

返回List&lt;Employee&gt; 的列表而不是List&lt;String&gt;,所以要么将结果类型更改为:

Map<String, List<Employee>> groupByTeachers = 
                // ...The rest of your code

或者如果您期望Map&lt;String, List&lt;String&gt;&gt;,则更改collectingAndThen 以获得期望的字段,例如getNamegetDep

e -> e.stream().sorted().limit(limit)
        .map(Employee::getDep) // for example getDep
        .collect(Collectors.toList())

【讨论】:

  • 第一种方法是抛出 ClassCastException.. :|
  • 酷,谢谢 YCF_L,
  • …或将Collectors.toList() 收集器替换为首先产生所需结果的东西,例如使用PriorityQueue
  • @Holger 你的意思是e -&gt; e.stream().sorted().map(Employee::getDept).collect(Collectors.toCollection(() -&gt; new PriorityQueue&lt;&gt;(limit))) ?
  • 不,这仍然是在groupingBy 之后发生的操作。我正在考虑Collectors.groupingBy(Employee::getDept, Collector.of( () -&gt; new PriorityQueue&lt;&gt;(), (q,e) -&gt; { q.add(e); if(q.size() &gt; limit) q.remove(); }, …)) 之类的东西(只是一个草图)。关键是收集器每组最多只能保存limit个元素,而不是把所有元素收集到一个列表中,然后再选择想要的元素。
【解决方案2】:

这里是有兴趣的人的最终答案。

Map<String, List<String>> groupByTeachers =
        listOfEmp.stream()
                .collect(
                        Collectors.groupingBy(
                                Employee::getDept,
                                Collectors.collectingAndThen(
                                        Collectors.toList(),
                                        e -> e.stream().sorted(Comparator.comparingLong(Employee::getSalary).reversed()).limit(limit).map(Employee::getName).collect(toList() ) ) ) );

【讨论】:

    【解决方案3】:

    您正在寻找的是 1. 按部门分组 2. 然后从分组的条目中,对值进行流式处理,根据属性对它们进行排序,最后限制它的 N 个值。这可以转换为代码:

    Map<String, List<String>> highestPaidEmployeesGroupByDepartment = listOfEmp.stream()
            .collect(Collectors.collectingAndThen(Collectors.groupingBy(Employee::getDept),
                    // part 2 starting here
                        m -> m.entrySet().stream()
                            .map(e -> Map.entry(e.getKey(), e.getValue().stream()
                                    .sorted(Comparator.comparing(Employee::getSalary).reversed())
                                    .limit(limit)
                                    .map(Employee::getName)
                                    .collect(Collectors.toList())))
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))));
    

    或者没有collectingAndThen 的替代方案是

    Map<String, List<String>> collect = listOfEmp.stream()
            .collect(Collectors.groupingBy(Employee::getDept))
            .entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
                    .sorted(Comparator.comparing(Employee::getSalary).reversed())
                    .limit(limit)
                    .map(Employee::getName)
                    .collect(Collectors.toList())));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-04
      • 1970-01-01
      • 2019-01-31
      • 1970-01-01
      • 2014-12-13
      • 1970-01-01
      相关资源
      最近更新 更多