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

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

persons.stream().distinct();

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

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

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


当前回答

也许会对某人有用。我还有一个要求。从第三方对象A列表中删除所有具有相同A.b字段的相同A.id的对象(列表中具有相同A.id的多个A对象)。流分区的答案由Tagir Valeev启发我使用自定义收集器返回Map<A。id列表< > >。简单的flatMap将完成其余的工作。

 public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) HashMap::new,
            (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
            (left, right) -> {
                left.putAll(right);
                return left;
            }, map -> new ArrayList<>(map.values()),
            Collector.Characteristics.UNORDERED)); }

其他回答

如果可以的话,我推荐使用Vavr。有了这个库,你可以做以下事情:

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection

您可以将person对象包装到另一个类中,该类只比较person的名称。之后,您将打开被包装的对象以再次获得人员流。流操作可能如下所示:

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

类Wrapper可能看起来如下所示:

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}

类似于Saeed Zarinfam使用的方法,但更像Java 8风格:)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(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>,但由于映射是内联的,所以它作为过滤器工作。这当然可读性较差,但有时避免使用这种方法是有帮助的。