Как применить фильтрацию в groupBy в потоках Java
Как вы группируете сначала, а затем применяете фильтрацию с использованием потоков 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]]}
Как мы можем это сделать?
Ответы
Ответ 1
ответ nullpointers показывает прямолинейный путь. Если вы не можете обновить до 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 9s, поэтому вам не нужно каким-либо образом изменять код, если вы используете import static
.
Ответ 2
Для этого вы можете использовать Collectors.filtering
API, представленный в Java-9:
Map<String, List<Employee>> output = list.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.filtering(e -> e.getSalary() > 2000, Collectors.toList())));
Важно из заметки API :
Коллекторы filtering() наиболее полезны при многоуровневом сокращении, например, ниже по течению от groupingBy
или partitioningBy
.
Фильтрующий коллектор отличается от операции потока filter()
.
Ответ 3
Используйте 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;}
)
);
Ответ 4
В 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
эту группу, а затем применила фильтрацию. фильтрующий коллектор предназначен для использования вместе с группировкой.
The Collectors.Filtering использует функцию фильтрации входных элементов и коллектора для сбора фильтрованных элементов:
list.stream().collect(Collectors.groupingBy(Employee::getDepartment),
Collectors.filtering(e->e.getSalary()>2000,toList());