34

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?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Manu Joy
  • 1,739
  • 6
  • 18
  • 30

4 Answers4

32

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());
Holger
  • 285,553
  • 42
  • 434
  • 765
  • wouldn't this create a new instance of TreeSet/ConcurrentSkipListSet for every element in the new list? – Vivek Kothari May 26 '18 at 06:09
  • 2
    @VivekKothari no, it's a fundamental property of method references of the form `expression::name` that the expression is evaluated first and the result captured by the functional interface instance which will only invoke the method on that object. See, for example “[What is the equivalent lambda expression for System.out::println?](https://stackoverflow.com/a/28025717/2711488)” Or [this Q&A](https://stackoverflow.com/q/37979731/2711488) or [that Q&A](https://stackoverflow.com/q/30514995/2711488) – Holger May 26 '18 at 11:00
1

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.

Mdhar9e
  • 1,376
  • 4
  • 23
  • 46
Dominic D'Souza
  • 961
  • 2
  • 7
  • 16
  • 4
    That is bad advice. Never override `equals` without also overriding `hashCode`. – Marvin Jun 16 '15 at 11:56
  • http://stackoverflow.com/questions/2265503/why-do-i-need-to-override-the-equals-and-hashcode-methods-in-java just read this and got to know why,,,thanks :) – Dominic D'Souza Jun 17 '15 at 09:56
1

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

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.

llogiq
  • 13,815
  • 8
  • 40
  • 72