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

Posted

技术标签:

【中文标题】Java Stream:通过布尔谓词分为两个列表【英文标题】:Java Stream: divide into two lists by boolean predicate 【发布时间】:2018-04-08 01:18:45 【问题描述】:

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

【问题讨论】:

How to partition a list by predicate using java8?的可能重复 @MalteHartwig 注意重复的 says “谓词”,但 OP 实际上是在询问按函数分组。不过,如果它像那样关闭,我不会重新打开。 【参考方案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 有几个原因(正如Juan Carlos Mendoza 所建议的那样):

首先,groupingBy 的参数是Function&lt;Employee, Boolean&gt;(在这种情况下),因此有可能向它传递一个可以返回 null 的函数,意味着如果这样,就会有第三个分区函数为任何员工返回 null。 partitioningBy 使用 Predicate&lt;Employee&gt;,因此它只能返回 2 个分区。 这将导致收集器抛出 NullPointerException:虽然没有明确记录,但会为空键明确抛出异常,大概是因为 Map.computeIfAbsent 的行为,即“如果函数返回 null,则不记录任何映射”,否则意味着元素将被默默地从输出中删除。 (感谢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 添加。

【讨论】:

第三,您获得的 Map 是内部优化的,仅包含两个键。 我对此很好奇:“传递给它一个可以返回 null 的函数,这意味着如果该函数返回 null 就会有第三个分区”。我创建了一个返回 null Stream.of(1,2,3,4).collect(groupingBy(x -&gt; x == 3 ? null : x &gt;= 3)) 的代码,执行后返回异常:java.lang.NullPointerException: element cannot be mapped to a null key。所以这不可能是真的。 @lczapski 很有趣,我会更新答案。不过,这实际上不是 documented。 @lczapski 我猜这个限制隐含来自Map.computeIfAbsent,它说“如果函数返回null,则不记录映射”。【参考方案2】:

在这种情况下,您也可以使用 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);

【讨论】:

+1,但请注意,使用这种方法应该稍微小心:groupingBy 的参数是Function&lt;Employee, Boolean&gt;,因此有可能通过它是一个可以返回 null 的函数,这意味着如果该函数为任何员工返回 null,则将存在第三个分区。 partitioningBy 使用 Predicate,因此它只能返回 2 个分区。 我只是做了一些实验,发现还有其他原因不使用groupingBy - 查看我的答案的编辑。 (对不起,我绝对不只是想撕掉你的答案,我实际上通过尝试这两个来学到一些东西!) @AndyTurner 谢谢。对于这种情况,我假设 isActive 不会返回 null(就像它使用原始布尔值一样)。 我想也是这样。我只是通过groupingBy 指出可能性【参考方案3】:

最复杂的方法是什么?

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

【讨论】:

【参考方案4】:

如果您愿意使用第三方库,则可以使用来自Eclipse Collections 的Collectors2.partition

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 的提交者。

【讨论】:

以上是关于Java Stream:通过布尔谓词分为两个列表的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个谓词应用于 java.util.Stream?

如何否定方法引用谓词

通过谓词限制流

通过谓词限制流

具有分配律的 Java 谓词

有没有办法在 Java 流上使用条件谓词?