Java Stream:通过布尔谓词分成两个列表

Java Stream: divide into two lists by boolean predicate

我有一个 employees 的列表。他们有 isActive 布尔字段。我想将 employees 分成两个列表:activeEmployeesformerEmployees。是否可以使用 Stream API?什么方法最精巧?

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>(在本例中),因此有可能向它传递一个可以 return null 的函数,意味着如果任何员工的函数 return 为空,则将有第三个分区。 partitioningBy 使用 Predicate<Employee>,因此它只能 return 2 个分区。 这将导致 NullPointerException 被收集器抛出:虽然没有记录明确地,为空键明确抛出异常,大概是因为 Map.computeIfAbsent that "If the function returns null no mapping is recorded", meaning elements would otherwise be dropped silently from the output. (Thanks to lczapski 指出这一点的行为)。

其次,您在结果地图中得到两个列表 (*) partitioningBy;使用 groupingBy,您只会得到 key/value 对,其中元素映射到给定键:

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, but it was added for Java 9.

中未记录此行为

您也可以在这种情况下使用 groupingBy,因为有 2 组可能性(活跃和不活跃的员工):

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

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

如果您愿意使用第三方库,这将使用 Collectors2.partition from 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 is a type that extends from PartitionIterablePartitionIterable 的每个子类型都有一个阳性结果 getSelected() 和阴性结果 getRejected() 的集合。

注意:我是 Eclipse Collections 的提交者。

What is the most sophisticated way?

Java 12 当然有新的 Collectors::teeing

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

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

一种更简单、更简洁的方法是像这样使用 stream.filter()collect()

activeEmployees = employees.stream().filter(Employee::isActive).collect(Collectors.toList());
formerEmployees = employees.stream().filter(employee -> !employee.isActive()).collect(Collectors.toList());