web-dev-qa-db-fra.com

Comment appliquer le filtrage sur le groupe By dans Java streams

Comment regroupez-vous d'abord, puis appliquez-vous le filtrage à l'aide des flux Java?

Exemple: Considérez cette classe Employee: Je veux regrouper par département avec une liste d'un employé ayant un salaire supérieur à 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;
    }
}   

Voilà comment je peux faire ça

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));  

Sortie

{A=[Employee [department=A, salary=5000, name=A1]],
 C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}

Comme il n'y a pas d'employés dans le département B avec un salaire supérieur à 2000. Donc, il n'y a pas de clé pour le département B: Mais en fait, je veux avoir cette clé avec une liste vide -

Sortie attendue

{A=[Employee [department=A, salary=5000, name=A1]],
 B=[],
 C=[Employee [department=C, salary=6000, name=C1], Employee [department=C, salary=7000, name=C2]]}

Comment pouvons-nous faire cela?

21
Niraj Sonawane

réponse de nullpointer montre la voie à suivre simple. Si vous ne pouvez pas mettre à jour vers Java 9, pas de problème, ce collecteur filtering n'est pas magique. Voici une version compatible 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]));
}

Vous pouvez l'ajouter à votre base de code et l'utiliser de la même manière que l'homologue de Java 9, afin de ne pas avoir à modifier le code de quelque manière que ce soit, si vous utilisez import static.

16
Holger

Vous pouvez utiliser pour cela: Collectors.filtering API introduite dans Java-9:

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

Important de la note de l'API :

  • Les collecteurs filtering () sont plus utiles lorsqu'ils sont utilisés dans une réduction à plusieurs niveaux, comme en aval d'un groupingBy ou partitioningBy.

  • Un collecteur de filtrage diffère de l'opération filter() d'un flux.

21
Naman

Utilisez Map#putIfAbsent(K,V) pour combler les lacunes après le filtrage

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()));

Remarque: Étant donné que la carte renvoyée par groupingBy n'est pas garantie d'être modifiable, vous devez spécifier un fournisseur de carte pour être sûr (merci à shmosel de l'avoir signalé).


Une autre solution (non recommandée) utilise toMap au lieu de groupingBy, ce qui a l'inconvénient de créer une liste temporaire pour chaque employé. Aussi ça a l'air un peu salissant

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;}
        )
);
6

Il n'y a aucun moyen plus propre de le faire dans Java 8:Holger a montré une approche claire en Java8 ici a accepté la réponse.

Voici comment je l'ai fait en Java 8:

Étape: 1 Grouper par département

Étape: 2 boucle jeter chaque élément et vérifier si le département a un employé avec un salaire> 2000

Étape: 3  mettre à jour la carte copier les valeurs dans une nouvelle carte basée sur 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 a ajouté un nouveau collecteur Collectors.filtering ce groupe d'abord, puis applique le filtrage. Le filtrage Collector est conçu pour être utilisé avec le regroupement .

Le Collectors.Filtering prend une fonction pour filtrer les éléments d'entrée et un collecteur pour collecter les éléments filtrés:

list.stream().collect(Collectors.groupingBy(Employee::getDepartment),
 Collectors.filtering(e->e.getSalary()>2000,toList());
2
Niraj Sonawane