在Java 8中,我如何使用流API通过检查每个对象的属性的清晰度来过滤一个集合?

例如,我有一个Person对象列表,我想删除同名的人,

persons.stream().distinct();

将对Person对象使用默认的相等性检查,所以我需要这样的东西,

persons.stream().distinct(p -> p.getName());

不幸的是,distinct()方法没有这样的重载。如果不修改Person类内部的相等检查,是否可以简洁地做到这一点?


当前回答

你可以使用groupingBy collector:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

如果你想有另一个流,你可以使用这个:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));

其他回答

将distinct视为一个有状态过滤器。下面是一个函数,它返回一个谓词,该谓词维护之前所见内容的状态,并返回给定元素是否第一次被看到:

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

然后你可以这样写:

persons.stream().filter(distinctByKey(Person::getName))

注意,如果流是有序的并且是并行运行的,这将保留副本中的任意一个元素,而不是像distinct()那样保留第一个元素。

(这本质上与我对这个问题的回答相同:Java Lambda Stream Distinct()对任意键?)

如果你想要名单,下面是最简单的方法

Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

此外,如果您想要查找不同的或唯一的名称列表,而不是Person,您也可以使用以下两个方法。

方法一:使用区别

persons.stream().map(x->x.getName()).distinct.collect(Collectors.toList());

方法二:使用HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));

因为每个人都在分享他们自己的想法和实施方法,我也有一个,它不是一个有效的方法,但它是有效的:

Set<String> personNameList = personList.stream().
map(tempPerson->tempPerson.getName()).collect(Collectors.toSet());

personList.stream().
                   collect(()->new ArrayList<Person>(),
                           (l1,p)->{
                                  if(!personNameList.contains(p.getName())) {
                                        l1.add(p);
                                  }
        }, ArrayList::addAll);

虽然迟到了,但我有时会用这句俏皮话作为等效:

((Function<Value, Key>) Value::getKey).andThen(new HashSet<>()::add)::apply

表达式是Predicate<Value>,但由于映射是内联的,所以它作为过滤器工作。这当然可读性较差,但有时避免使用这种方法是有帮助的。

我想改进一下斯图尔特·马克斯的回答。如果键是空的,它会通过NullPointerException。在这里,我通过添加一个检查keyExtractor.apply(t)!=null来忽略空键。

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> keyExtractor.apply(t)!=null && seen.add(keyExtractor.apply(t));

}