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

如何先分组,然后使用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 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> 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]]} 

我们应该怎么做?

nullpointer的答案显示了直截了当的方式。 如果你不能更新到Java 9,没问题,这个filtering收集器是没有魔力的。 这是Java 8兼容版本:

 public static  Collector filtering( Predicate predicate, Collector downstream) { BiConsumer 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 ,则无需以任何方式更改代码。

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

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

API说明中的重要内容:

  • filter()收集器在多级缩减中使用时非常有用,例如groupingBypartitioningBy下游。

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

使用Map#putIfAbsent(K,V)填充过滤后的间隙

 Map> 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 ,这有一个缺点, toMap每个Employee创建一个临时列表。 它看起来有点乱

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

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

这是我在java 8中完成它的方式:

步骤:1按部门分组

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

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

 Map> employeeMap = list.stream().collect(Collectors.groupingBy(Employee::getDepartment)); Map> newMap = new HashMap>(); 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());