У меня есть список employees
. Они имеют isActive
булево поле. Я хотел бы разделить employees
на два списка: activeEmployees
и formerEmployees
. Можно ли использовать Stream API? Какой самый сложный способ?
Java Stream: разделите на два списка по логическому предикату
Ответ 1
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