Подтвердить что ты не робот

Как применить фильтрацию в 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]]}

Как мы можем это сделать?

4b9b3361

Ответ 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());