如何在 java 流中对 groupBy 应用过滤
How to apply Filtering on groupBy in java streams
如何先分组,然后使用 Java 流应用过滤?
示例:考虑这个 Employee
class:
我想按部门对薪水大于 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部门的key:
但实际上,我想要那个带有空列表的键 –
预期输出
{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 8 中没有更简洁的方法: Holger has shown clear approach in 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-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 注释 的重要信息:
The filtering() collectors are most useful when used in a multi-level reduction, such as downstream of a groupingBy
or partitioningBy
.
A filtering collector differs from a stream's filter()
operation.
过滤后使用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 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
,则无需以任何方式更改代码.
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())
)
);
如何先分组,然后使用 Java 流应用过滤?
示例:考虑这个 Employee
class:
我想按部门对薪水大于 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部门的key: 但实际上,我想要那个带有空列表的键 –
预期输出
{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 8 中没有更简洁的方法: Holger has shown clear approach in 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-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 注释 的重要信息:
The filtering() collectors are most useful when used in a multi-level reduction, such as downstream of a
groupingBy
orpartitioningBy
.A filtering collector differs from a stream's
filter()
operation.
过滤后使用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;}
)
);
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
,则无需以任何方式更改代码.
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())
)
);