web-dev-qa-db-fra.com

Java Stream: divisé en deux listes par prédicat booléen

J'ai une liste de employees. Ils ont isActive champ booléen. Je voudrais diviser employees en deux listes: activeEmployees et formerEmployees. Est-il possible de faire en utilisant l'API Stream? Quelle est la manière la plus sophistiquée?

19
Rudziankoŭ

Collectors.partitioningBy :

Map<Boolean, List<Employee>> partitioned = 
    listOfEmployees.stream().collect(
        Collectors.partitioningBy(Employee::isActive));

La carte résultante contient deux listes, correspondant à la correspondance ou non du prédicat:

List<Employee> activeEmployees = partitioned.get(true);
List<Employee> formerEmployees = partitioned.get(false);

Il y a plusieurs raisons d'utiliser partitioningBy plutôt que groupingBy (comme suggéré par Juan Carlos Mendoza ):

Premièrement, le paramètre de groupingBy est un Function<Employee, Boolean> (dans ce cas), et donc il est possible de lui passer une fonction qui peut retourner null, ce qui signifie qu'il y aurait une 3e partition si cette fonction retourne null pour l'un des employés. partitioningBy utilise un Predicate<Employee>, il ne peut donc renvoyer que 2 partitions. ce qui entraînerait la génération d'un NullPointerException par le collecteur: bien qu'elle ne soit pas explicitement documentée, une exception est explicitement levée pour les clés nulles, probablement en raison du comportement de Map.computeIfAbsent que "Si la fonction renvoie null, aucun mappage n'est enregistré", ce qui signifie que les éléments seraient sinon supprimés silencieusement de la sortie. (Merci à lczapski pour l'avoir signalé).

Deuxièmement, vous obtenez deux listes (*) dans la carte résultante avec partitioningBy; avec groupingBy, vous obtenez uniquement des paires clé/valeur où les éléments correspondent à la clé donnée:

System.out.println(
    Stream.empty().collect(Collectors.partitioningBy(a -> false)));
// Output: {false=[], true=[]}

System.out.println(
    Stream.empty().collect(Collectors.groupingBy(a -> false)));
// Output: {}

(*) Ce comportement n'est pas documenté dans Java 8 Javadoc , mais il a été ajouté pour Java 9 .

16
Andy Turner

Quelle est la manière la plus sophistiquée?

Java 12 bien sûr avec le nouveau Collectors::teeing

List<List<Emploee>> divided = employees.stream().collect(
      Collectors.teeing(
              Collectors.filtering(Emploee::isActive, Collectors.toList()),
              Collectors.filtering(Predicate.not(Emploee::isActive), Collectors.toList()),
              List::of
      ));

System.out.println(divided.get(0));  //active
System.out.println(divided.get(1));  //inactive
2
Adrian

Vous pouvez également utiliser groupingBy dans ce cas car il existe 2 possibilités de groupe (employés actifs et inactifs):

Map<Boolean, List<Employee>> grouped = employees.stream()
                .collect(Collectors.groupingBy(Employee::isActive));

List<Employee> activeEmployees = grouped.get(true);
List<Employee> formerEmployees = grouped.get(false);
2

Si vous êtes prêt à utiliser une bibliothèque tierce, cela fonctionnera en utilisant Collectors2.partition de Collections Eclipse .

PartitionMutableList<Employee> partition =
        employees.stream().collect(
                Collectors2.partition(Employee::isActive, PartitionFastList::new));

List<Employee> activeEmployees = partition.getSelected();
List<Employee> formerEmployees = partition.getRejected();

Vous pouvez également simplifier les choses en utilisant ListIterate.

PartitionMutableList<Employee> partition =
        ListIterate.partition(employees, Employee::isActive);

List<Employee> activeEmployees = partition.getSelected();
List<Employee> formerEmployees = partition.getRejected();

PartitionMutableList est un type qui s'étend de PartitionIterable . Chaque sous-type de PartitionIterable a une collection pour les résultats positifs getSelected() et les résultats négatifs getRejected().

Remarque: je suis un committer pour les collections Eclipse.

2
Donald Raab