繁体   English   中英

如何在java流中对groupBy应用过滤

[英]How to apply Filtering on groupBy in java streams

您如何先分组,然后使用 Java 流应用过滤?

示例:考虑这个Employee类:我想按部门分组,其中包含薪水大于 2000 的员工列表。

public class Employee {
    private String department;
    private Integer salary;
    private String name;

    //getter and setter

    public Employee(String department, Integer salary, String name) {
        this.department = department;
        this.salary = salary;
        this.name = name;
    }
}   

这就是我可以做到的

List<Employee> list   = new ArrayList<>();
list.add(new Employee("A", 5000, "A1"));
list.add(new Employee("B", 1000, "B1"));
list.add(new Employee("C", 6000, "C1"));
list.add(new Employee("C", 7000, "C2"));

Map<String, List<Employee>> collect = list.stream()
    .filter(e -> e.getSalary() > 2000)
    .collect(Collectors.groupingBy(Employee::getDepartment));  

输出

{A=[Employee [department=A, salary=5000, name=A1]],
 C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}

因为 B 部门没有工资大于 2000 的员工,所以 B 部门没有键:但实际上,我想要那个键是空列表——

预期输出

{A=[Employee [department=A, salary=5000, name=A1]],
 B=[],
 C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}

我们怎样才能做到这一点?

您可以使用自 Java-9 以来引入的Collectors.filtering API:

Map<String, List<Employee>> output = list.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment,
                    Collectors.filtering(e -> e.getSalary() > 2000, Collectors.toList())));

API 说明中的重要内容:

  • filter() 收集器在用于多级归约时最有用,例如groupingBypartitioningBy下游。

  • 过滤收集器不同于流的filter()操作。

nullpointer 的回答显示了直接的方法。 如果你不能更新到 Java 9,没问题,这个filtering收集器不是魔术。 这是 Java 8 兼容版本:

public static <T, A, R> Collector<T, ?, R> filtering(
    Predicate<? super T> predicate, Collector<? super T, A, R> downstream) {

    BiConsumer<A, ? super T> accumulator = downstream.accumulator();
    return Collector.of(downstream.supplier(),
        (r, t) -> { if(predicate.test(t)) accumulator.accept(r, t); },
        downstream.combiner(), downstream.finisher(),
        downstream.characteristics().toArray(new Collector.Characteristics[0]));
}

您可以将它添加到您的代码库中,并以与 Java 9 的对应方式相同的方式使用它,因此如果您使用import static ,则无需以任何方式更改代码。

过滤后使用Map#putIfAbsent(K,V)填补空白

Map<String, List<Employee>> map = list.stream()
              .filter(e->e.getSalary() > 2000)
              .collect(Collectors.groupingBy(Employee::getDepartment, HashMap::new, toList()));
list.forEach(e->map.putIfAbsent(e.getDepartment(), Collections.emptyList()));

注意:由于 groupingBy 返回的地图不能保证是可变的,您需要指定一个地图供应商来确定(感谢 shmosel 指出这一点)。


另一个(不推荐)解决方案是使用toMap而不是groupingBy ,它的缺点是为每个员工创建一个临时列表。 而且看起来有点乱

Predicate<Employee> filter = e -> e.salary > 2000;
Map<String, List<Employee>> collect = list.stream().collect(
        Collectors.toMap(
            e-> e.department, 
            e-> new ArrayList<Employee>(filter.test(e) ? Collections.singleton(e) : Collections.<Employee>emptyList()) , 
            (l1, l2)-> {l1.addAll(l2); return l1;}
        )
);

在 Java 8 中没有更简洁的方法来做到这一点: Holger已经在 java8 中展示了明确的方法,这里接受了答案。

这就是我在 Java 8 中的做法:

步骤:1按部门分组

步骤:2循环抛出每个元素并检查部门是否有薪水> 2000的员工

步骤:3基于noneMatch更新新地图中的地图副本值

Map<String, List<Employee>> employeeMap = list.stream().collect(Collectors.groupingBy(Employee::getDepartment));
Map<String, List<Employee>> newMap = new HashMap<String,List<Employee>>();
         employeeMap.forEach((k, v) -> {
            if (v.stream().noneMatch(emp -> emp.getSalary() > 2000)) {
                newMap.put(k, new ArrayList<>());
            }else{
                newMap.put(k, v);
           }

        });

Java 9:Collectors.filtering

java 9 添加了新的收集器Collectors.filtering这个组,然后应用过滤。 过滤收集器旨在与分组一起使用。

Collectors.Filtering 接受一个过滤输入元素的函数和一个收集器来收集过滤的元素:

list.stream().collect(Collectors.groupingBy(Employee::getDepartment),
 Collectors.filtering(e->e.getSalary()>2000,toList());

Java 8 版本:您可以按部门进行分组,然后流式传输条目集并通过在过滤器中添加谓词再次执行收集:

    Map<String, List<Employee>> collect = list.stream()
        .collect(Collectors.groupingBy(Employee::getDepartment)).entrySet()
        .stream()
        .collect(Collectors.toMap(Map.Entry::getKey,
            entry -> entry.getValue()
                .stream()
                .filter(employee -> employee.getSalary() > 2000)
                .collect(toList())
            )
        );

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM