Java Stream: разделите на два списка по логическому предикату

У меня есть список employees. Они имеют isActive булево поле. Я хотел бы разделить employees на два списка: activeEmployees и formerEmployees. Можно ли использовать Stream API? Какой самый сложный способ?

Ответ 1

Collectors.partitioningBy:

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

Полученная карта содержит два списка, соответствующих тому, был ли сопоставлен предикат:

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

Есть несколько причин использовать partitioningBy вместо groupingBy (как предложено Хуаном Карлосом Мендосой):

Во-первых, параметр groupingBy является Function<Employee, Boolean> (в данном случае), и поэтому существует возможность передачи ему функции, которая может возвращать ноль, означает, что будет 3-й раздел, если эта функция вернет ноль для любого из сотрудников. partitioningBy использует Predicate<Employee>, поэтому он может вернуть только 2 раздела., что приведет к тому, что NullPointerException будет сгенерирован коллектором: хотя это явно не задокументировано, исключение явно генерируется для нулевых ключей, предположительно из-за поведения Map.computeIfAbsent, что "если функция возвращает нулевое значение, сопоставление не записывается", это означает, что элементы в противном случае были бы удалены без вывода из вывода. (Спасибо lczapski за указание на это).

Во-вторых, вы получаете два списка (*) в результирующей карте с partitioningBy; с groupingBy вы получаете только пары ключ/значение, в которых элементы отображаются на данный ключ:

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

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

(*) Это поведение не описано в Java 8 Javadoc, но было добавлено для Java 9.

Ответ 2

Вы также можете использовать groupingBy в этом случае, поскольку есть две возможности группы (активные и неактивные сотрудники):

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

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

Ответ 3

Если вы открыты для использования сторонней библиотеки, это будет работать с использованием Collectors2.partition из Eclipse Collections.

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

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

Вы также можете упростить вещи, используя ListIterate.

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

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

PartitionMutableList - это тип, который происходит от PartitionIterable. Каждый подтип PartitionIterable имеет коллекцию для положительных результатов getSelected() и отрицательных результатов getRejected().

Примечание: я являюсь коммиттером для Eclipse Collections.

Ответ 4

  Какой самый сложный способ?

Java 12 конечно с новым 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