Merging two List of objects in java 8

2019-02-02 09:26发布

问题:

I have a Java class Parent with 20 attributes (attrib1, attrib2 .. attrib20) and its corresponding getters and setters. Also I have two lists of Parent objects: list1 and list2.

Now I want to merge both lists and avoid duplicate objects based on attrib1 and attrib2.

Using Java 8:

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

But in which place I have to specify the attributes? Should I override hashCode and equals method?

回答1:

If you want to implement equals and hashCode, the place to do it is inside the class Parent. Within that class add the methods like

    @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());
    }

If you did this, distinct() invoked on a Stream<Parent> will automatically do the right thing.


If you don’t want (or can’t) change the class Parent, there is no delegation mechanism for equality, but you may resort to ordering as that has a delegation mechanism:

Comparator<Parent> c=Comparator.comparing(Parent::getAttrib1)
        .thenComparing(Parent::getAttrib2)
        .thenComparing(Parent::getAttrib3)
        // …
        .thenComparing(Parent::getAttrib19)
        .thenComparing(Parent::getAttrib20);

This defines an order based on the properties. It requires that the types of the attributes itself are comparable. If you have such a definition, you can use it to implement the equivalent of a distinct(), based on that Comparator:

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

There is also a thread-safe variant, in case you want to use it with parallel streams:

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


回答2:

For example:

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);
    }
}


回答3:

If you want to override .equals(…) and .hashCode(), you need to do so on the Parent class. Note that this may cause other uses of Parent to fail. Alexis C.'s linked solution is more conservative.



回答4:

Override the equals and hashCode methods in Parent class to avoid duplicates from the lists. This will give you the exact result what you want.