在Java 8中,如何通过检查每个对象的属性的清晰度来使用 Stream
API过滤集合?
例如,我有一个 Person
对象的列表,我想删除具有相同名称的人,
persons.stream().distinct();
将使用 Person
对象的默认相等检查,所以我需要像,
persons.stream().distinct(p -> p.getName());
不幸的是 distinct()
方法没有这样的重载 . 如果不修改 Person
类中的相等性检查,是否可以简洁地执行此操作?
20 回答
使用带有自定义比较器的TreeSet有一种更简单的方法 .
我们也可以使用RxJava(非常强大的reactive extension库)
要么
考虑
distinct
是一个有状态过滤器 . 这是一个函数,它返回一个谓词,该谓词维护前面所看到的状态,并返回给定元素是否第一次被看到:然后你可以写:
请注意,如果流是有序的并且并行运行,这将保留重复项中的任意元素,而不是第一个,如
distinct()
.(这与此问题的my answer基本相同:Java Lambda Stream Distinct() on arbitrary key?)
另一种方法是使用名称作为关键字将人员放在 Map 中:
请注意,如果名称重复,则保留的Person将是第一个被限制的人 .
您可以将person对象包装到另一个类中,该类仅比较人员的名称 . 然后,您打开包装的对象以再次获取人流 . 流操作可能如下所示:
类
Wrapper
可能如下所示:另一种解决方案,使用
Set
. 可能不是理想的解决方案,但它确实有效或者,如果您可以修改原始列表,则可以使用removeIf方法
您可以在Eclipse Collections中使用
distinct(HashingStrategy)
方法 .如果您可以重构
persons
来实现Eclipse Collections接口,则可以直接在列表上调用该方法 .HashingStrategy只是一个策略接口,允许您定义equals和hashcode的自定义实现 .
注意:我是Eclipse Collections的提交者 .
扩展Stuart Marks的答案,这可以用更短的方式完成,没有并发映射(如果你不需要并行流):
然后打电话:
你可以使用
groupingBy
collector:如果你想拥有另一个流,你可以使用它:
我做了一个通用版本:
一个例子:
如果可以,我建议使用Vavr . 使用此库,您可以执行以下操作:
你可以使用StreamEx库:
Saeed Zarinfam使用的类似方法,但更多Java 8风格:)
另一个支持它的库是jOOλ,它的Seq.distinct(Function<T,U>)方法:
Under the hood,它与accepted answer几乎完全相同 .
实现此目的的最简单方法是跳转排序功能,因为它已经提供了可选的
Comparator
,可以使用元素的属性创建 . 然后你必须过滤掉可以使用statefullPredicate
完成的重复项,它使用的事实是,对于排序的流,所有相等的元素都是相邻的:当然,statefull
Predicate
不是线程安全的,但是如果你需要的话,你可以将这个逻辑移到Collector
中,让流在使用你的Collector
时处理线程安全 . 这取决于你想要对你在问题中没有告诉我们的不同元素流做什么 .在@ josketres的回答基础上,我创建了一个通用的实用方法:
你可以通过创建一个Collector来使这个Java 8更加友好 .
也许对某些人有用 . 我还有一点要求 . 拥有来自第三方的对象列表
A
将删除所有具有相同A.id
字段的相同A.id
(列表中具有相同A.id
的多个A
对象) . Stream partition回答Tagir Valeev启发我使用自定义Collector
返回Map<A.id, List<A>>
. 简单的flatMap
将完成其余的工作 .你可以写的最简单的代码:
使用以下两种方法也可以找到不同或唯一的列表 .
方法1:使用Distinct
方法2:使用HashSet