Java Stream:通过布尔谓词分为两个列表
Posted
技术标签:
【中文标题】Java Stream:通过布尔谓词分为两个列表【英文标题】:Java Stream: divide into two lists by boolean predicate 【发布时间】:2018-04-08 01:18:45 【问题描述】:我有一个employees
的列表。他们有 isActive
布尔字段。我想将employees
分成两个列表:activeEmployees
和formerEmployees
。是否可以使用 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<Employee, Boolean>
(在这种情况下),因此有可能向它传递一个可以返回 null 的函数,意味着如果这样,就会有第三个分区函数为任何员工返回 null。 这将导致收集器抛出 partitioningBy
使用 Predicate<Employee>
,因此它只能返回 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 就会有第三个分区”。我创建了一个返回 nullStream.of(1,2,3,4).collect(groupingBy(x -> x == 3 ? null : x >= 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<Employee, Boolean>
,因此有可能通过它是一个可以返回 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:通过布尔谓词分为两个列表的主要内容,如果未能解决你的问题,请参考以下文章