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

Nir*_*ane 22 java java-8 java-stream collectors java-9

如何先分组,然后使用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;
    }
}   
Run Code Online (Sandbox Code Playgroud)

这就是我如何做到这一点

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));  
Run Code Online (Sandbox Code Playgroud)

产量

{A=[Employee [department=A, salary=5000, name=A1]],
 C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}
Run Code Online (Sandbox Code Playgroud)

因为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]]}
Run Code Online (Sandbox Code Playgroud)

我们应该怎么做?

Nam*_*man 24

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

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

API说明中的重要内容:

  • filter()收集器在多级缩减中使用时非常有用,例如a groupingBy或者下游partitioningBy.

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

  • 当你将`filtering(...)`直接传递给`collect`方法时,@ shmosel也会这样做,例如`filtering(...,groupingBy(...))`.但是当你将它作为下游收集器传递给`groupingBy`时,即`groupingBy(...,filtering(...))`,它将在创建组后接收元素.它就像那样简单,类似于`mapping`或`flatMapping`的工作方式. (4认同)

Hol*_*ger 18

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]));
}
Run Code Online (Sandbox Code Playgroud)

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


Mic*_*ath 6

使用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()));
Run Code Online (Sandbox Code Playgroud)

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


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

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;}
        )
);
Run Code Online (Sandbox Code Playgroud)

  • `groupingBy()`返回的映射不保证是可变的.您需要使用"供应商"超载. (3认同)