在Java 8中,我如何使用流API通过检查每个对象的属性的清晰度来过滤一个集合?
例如,我有一个Person对象列表,我想删除同名的人,
persons.stream().distinct();
将对Person对象使用默认的相等性检查,所以我需要这样的东西,
persons.stream().distinct(p -> p.getName());
不幸的是,distinct()方法没有这样的重载。如果不修改Person类内部的相等检查,是否可以简洁地做到这一点?
在Java 8中,我如何使用流API通过检查每个对象的属性的清晰度来过滤一个集合?
例如,我有一个Person对象列表,我想删除同名的人,
persons.stream().distinct();
将对Person对象使用默认的相等性检查,所以我需要这样的东西,
persons.stream().distinct(p -> p.getName());
不幸的是,distinct()方法没有这样的重载。如果不修改Person类内部的相等检查,是否可以简洁地做到这一点?
当前回答
这就像一个魅力:
按唯一键对数据进行分组,形成映射。 返回映射的每个值的第一个对象(可以有多个具有相同名称的人)。
persons.stream()
.collect(groupingBy(Person::getName))
.values()
.stream()
.flatMap(values -> values.stream().limit(1))
.collect(toList());
其他回答
扩展Stuart Marks的回答,这可以用更短的方式完成,不需要并发映射(如果你不需要并行流):
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
final Set<Object> seen = new HashSet<>();
return t -> seen.add(keyExtractor.apply(t));
}
然后调用:
persons.stream().filter(distinctByKey(p -> p.getName());
实现这一点最简单的方法是跳到sort特性上,因为它已经提供了一个可选的Comparator,可以使用元素的属性创建。然后你必须过滤掉重复项,这可以使用一个状态完备的Predicate来完成,它使用的事实是,对于一个已排序的流,所有相等的元素是相邻的:
Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
Person previous;
public boolean test(Person p) {
if(previous!=null && c.compare(previous, p)==0)
return false;
previous=p;
return true;
}
})./* more stream operations here */;
当然,一个有状态的Predicate不是线程安全的,但是如果你需要,你可以把这个逻辑移到一个Collector中,让流在使用你的Collector时处理线程安全。这取决于你想如何处理你在问题中没有告诉我们的不同元素流。
不同的对象列表可以使用:
List distinctPersons = persons.stream()
.collect(Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
ArrayList::new));
您可以在Eclipse Collections中使用distinct(HashingStrategy)方法。
List<Person> persons = ...;
MutableList<Person> distinct =
ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));
如果可以重构人员以实现Eclipse Collections接口,则可以直接调用列表上的方法。
MutableList<Person> persons = ...;
MutableList<Person> distinct =
persons.distinct(HashingStrategies.fromFunction(Person::getName));
HashingStrategy只是一个策略接口,允许您定义equals和hashcode的自定义实现。
public interface HashingStrategy<E>
{
int computeHashCode(E object);
boolean equals(E object1, E object2);
}
注意:我是Eclipse Collections的提交者。
虽然迟到了,但我有时会用这句俏皮话作为等效:
((Function<Value, Key>) Value::getKey).andThen(new HashSet<>()::add)::apply
表达式是Predicate<Value>,但由于映射是内联的,所以它作为过滤器工作。这当然可读性较差,但有时避免使用这种方法是有帮助的。