在Java 8中合并两个对象列表

34

我有一个Java类Parent,它有20个属性(attrib1, attrib2 .. attrib20)和对应的getter和setter。同时,我有两个Parent对象列表:list1list2

现在我想合并这两个列表并根据attrib1attrib2避免重复对象。

使用Java 8:

List<Parent> result = Stream.concat(list1.stream(), list2.stream())
                .distinct()
                .collect(Collectors.toList());   

但是我需要在哪里指定这些属性呢?我应该重写hashCodeequals方法吗?


使用Set来避免重复值。 - Noundla Sandeep
1
请参见https://dev59.com/mV4c5IYBdhLWcg3weqW9#27872086。 - Alexis C.
1
你的回声功能很酷 :) - Chetan Kinger
4个回答

32
如果您想要实现 equals 和 hashCode 方法,那么需要在 Parent 类中进行。在该类内部添加方法,例如:
    @Override
    public int hashCode() {
        return Objects.hash(getAttrib1(), getAttrib2(), getAttrib3(),
            // …
                            getAttrib19(), getAttrib20());
    }

    @Override
    public boolean equals(Object obj) {
        if(this==obj) return true;
        if(!(obj instanceof Parent)) return false;
        Parent p=(Parent) obj;
        return Objects.equals(getAttrib1(), p.getAttrib1())
            && Objects.equals(getAttrib2(), p.getAttrib2())
            && Objects.equals(getAttrib3(), p.getAttrib3())
            // …
            && Objects.equals(getAttrib19(), p.getAttrib19())
            && Objects.equals(getAttrib20(), p.getAttrib20());
    }
如果您这样做了,对于一个 Stream<Parent> 调用 distinct() 将自动执行正确的操作。
如果您不想(或无法)更改 Parent 类,那么就没有委托机制来实现相等性,但是您可以诉诸 排序 ,因为它具有委托机制。
Comparator<Parent> c=Comparator.comparing(Parent::getAttrib1)
        .thenComparing(Parent::getAttrib2)
        .thenComparing(Parent::getAttrib3)
        // …
        .thenComparing(Parent::getAttrib19)
        .thenComparing(Parent::getAttrib20);

这定义了基于属性的顺序。它要求属性本身具有可比性。如果您有这样的定义,您可以使用它来实现基于该 Comparatordistinct() 相当于:

List<Parent> result = Stream.concat(list1.stream(), list2.stream())
        .filter(new TreeSet<>(c)::add)
        .collect(Collectors.toList());

还有一个线程安全的变体,以防您想要在并行流中使用它:

List<Parent> result = Stream.concat(list1.stream(), list2.stream())
        .filter(new ConcurrentSkipListSet<>(c)::add)
        .collect(Collectors.toList());

这样做会不会为新列表中的每个元素创建一个新的TreeSet/ConcurrentSkipListSet实例? - Vivek Kothari
2
@VivekKothari 不是的,expression::name 形式的方法引用的基本属性是先评估表达式,然后由函数接口实例捕获结果,该实例仅在该对象上调用方法。例如,请参见“System.out::println 的等效 lambda 表达式是什么?” 或 此问答那个问答 - Holger

1

Parent类中重写equalshashCode方法,以避免列表中的重复项。这将给您带来确切的想要的结果。


4
那是个糟糕的建议。如果覆盖了 equals 方法,就一定要同时重新实现 hashCode 方法。 - Marvin
刚刚读了这篇文章,现在知道为什么需要在Java中重写equals和hashCode方法了,谢谢 :) - Dominic D'Souza

1

例如:

public class Parent {

    public int no;
    public String name;

    @Override
    public int hashCode() {
        return (no << 4) ^ name.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Parent))
            return false;
        Parent o = (Parent)obj;
        return this.no == o.no && this.name.equals(o.name);
    }
}

0
如果您想覆盖.equals(…).hashCode(),则需要在Parent类上这样做。请注意,这可能会导致其他对Parent的使用失败。Alexis C.的链接解决方案更加保守。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接