Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to override equals(), hashcode() and compareTo() for a HashSet

I am trying to override the mentioned methods for my HashSet:

Set<MyObject> myObjectSet = new HashSet<MyObject>();

MyObject:

public class MyObject implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  Long id;
  String name;
  int number;
  Map<String,String> myMap;

  public MyObject(String name, int number, Map<String,String> myMap) {
    this.name = name;
    this.number = number;
    this.myMap = myMap;
  }

  [...]
}

How do I override the hashcode(), equals() and compareTo() method?


Currently I have the following:

public int hashCode () {
  return id.hashCode();
}

// override the equals method.
public boolean equals(MyObject s) {
  return id.equals(s.id);
}

// override compareTo
public int compareTo(MyObject s) {
  return id.compareTo(s.id);
}    

I read that comparing by id is not enough this is object is a persistent entity for the DB (see here).

The name and number aren't unique across all objects of this type.

So how should I override it?
Do I also need to compare the hashMap inside it?

I am confused. The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.

How do I check for its equality?

Based on all the responses I have changed the methods to the following

 @Override
    public boolean equals(final Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    final MyComplexObj myComplexObj = (MyComplexObj) o;

    return myMap != null ? myMap.equals(myComplexObj.myMap) : myComplexObj.myMap == null;
    }

    @Override
    public int hashCode() {
    return myMap != null ? myMap.hashCode() : 0;
    }



    public int compareTo(MyComplexObj o) {
    return myMap.compareTo(o.getMyMap()));
    }

This fails at the compareTo method, "this method is undefined for the type Map

like image 392
user_mda Avatar asked Jun 16 '17 18:06

user_mda


People also ask

How do you correctly override the hashCode () and equals () methods?

if a class overrides equals, it must override hashCode. when they are both overridden, equals and hashCode must use the same set of fields. if two objects are equal, then their hashCode values must be equal as well. if the object is immutable, then hashCode is a candidate for caching and lazy initialization.

What happens if we do not override hashCode () and equals () in HashSet?

now if you have not override the hashcode and equals then after putting all the objects till line 5 if you put obj5 in the map as By Default HashCode you get different hashCode so the row(Bucket will be different). So in runtime memory it will be stored like this.

Which class does override the equals () and hashCode () methods?

The Team class overrides only equals(), but it still implicitly uses the default implementation of hashCode() as defined in the Object class. And this returns a different hashCode() for every instance of the class.

Does HashSet use equals or hashCode?

The HashSet takes advantage of hashcodes to speed things up. It assumes that two objects that equal eachother will have the same hash code. However it does not assume that two objects with the same hash code mean they are equal.


2 Answers

The basic question here is "How can you determine if two objects are equal to each other?"

This is a simple question for simple objects. However, it becomes increasingly difficult with even slightly more complex objects.

As stated in the original question:

The only unique thing about the object is the the map myMap which gets populated later in the lifecycle.

Given two instances of the type MyObject, the member variables myMap must be compared with each other. This map is of type Map<String, String>. A few questions immediately come to mind:

  • How do the keys & values define equality?
    • (does a key=value pair need to be compared as a unit?)
    • (or should only the values be compared to each other?)
  • How does the order of the keys in the map affect equality?
    • (should keys in the list be sorted, so that A-B-C is equivalent to B-C-A?)
    • (or does 1-2-3 mean something different than 3-2-1?)
  • Does upper/lower case make any different to the equality of the values?
  • Will these objects ever be stored in some kind of Java HashSet or Java TreeSet?
    • (do you need to store the same object several times in the same collection?)
    • (or should objects with equal hashcodes only be stored once?)
  • Will these objects ever require sorting as part of a list or Java Collection?
  • How should the comparison function arrange non-equal objects in a list?
    • (how should key order determine if an object will come earlier or later in a list?)
    • (how should values determine order, especially if several values are different?)

Answers to each of these questions will vary between applications. In order to keep this applicable to a general audience, the following assumptions are being made:

  • To maintain a deterministic comparison, keys will be sorted
  • Values will be considered to be case-sensitive
  • Keys and values are inseparable, and will be compared as a unit
  • The Map will be flattened into a single String, so results can be compared easily

The beauty of using equals(), hashCode(), and compareTo() is that once hashCode() is implemented properly, the other functions can be defined based on hashCode().

Considering all of that, we have the following implementation:

@Override
public boolean equals(final Object o)
{
    if (o instanceof MyObject)
    {
        return (0 == this.compareTo(((MyObject) o)));
    }
    return false;
}

@Override
public int hashCode()
{
    return getKeyValuePairs(this.myMap).hashCode();
}

// Return a negative integer, zero, or a positive integer
// if this object is less than, equal to, or greater than the other object
public int compareTo(final MyObject o)
{
    return this.hashCode() - o.hashCode();
}

// The Map is flattened into a single String for comparison
private static String getKeyValuePairs(final Map<String, String> m)
{
    final StringBuilder kvPairs = new StringBuilder();

    final String kvSeparator = "=";
    final String liSeparator = "^";

    if (null != m)
    {
        final List<String> keys = new ArrayList<>(m.keySet());
        Collections.sort(keys);

        for (final String key : keys)
        {
            final String value = m.get(key);
            kvPairs.append(liSeparator);
            kvPairs.append(key);
            kvPairs.append(kvSeparator);
            kvPairs.append(null == value ? "" : value);
        }
    }

    return 0 == kvPairs.length() ? "" : kvPairs.substring(liSeparator.length());
}

All the critical work is being done inside of hashCode(). For sorting, the compareTo() function only needs to return a negative/zero/positive number -- a simple hashCode() diff. And the equals() function only needs to return true/false -- a simple check that compareTo() equals zero.


For further reading, there is a famous dialogue by Lewis Carroll on the foundations of logic, which touches on the basic question of equality:

https://en.wikipedia.org/wiki/What_the_Tortoise_Said_to_Achilles

And, in regard to even simple grammatical constructs, there is a fine example of two "equal" sentences at the start of chapter 6, "Pig and Pepper", from Alice in Wonderland:

The Fish-Footman began by producing from under his arm a great letter, and this he handed over to the other, saying, in a solemn tone, "For the Duchess. An invitation from the Queen to play croquet." The Frog-Footman repeated, in the same solemn tone, "From the Queen. An invitation for the Duchess to play croquet." Then they both bowed low and their curls got entangled together.

like image 104
JonathanDavidArndt Avatar answered Oct 14 '22 09:10

JonathanDavidArndt


compareTo() is relevant to sorting. It has no relevance to a HashSet or HashMap.

A properly working equals() and hashCode() are vital for members of hash-based collections. Read their specifications in the Javadoc for Object.

Possibly the definitive recommendations for implementing these are in Joshua Bloch's Effective Java. I recommend reading the relevant chapter -- it's easily Google-able. There's no point in trying to paraphrase it all here.


One thing that may have escaped your notice, is that your field myMap has a working equals() and hashCode() of its own, so you don't have to do anything special with it. If you can guarantee that none of the fields are null, a reasonable hashCode() would be (following Bloch's system):

public int hashCode() {
     int result = 44; // arbitrarily chosen
     result = 31 * result + (int) (id ^ (id >>> 32));
     result = 31 * result + name.hashCode();
     result = 31 * result + number;
     result = 31 * result + myMap.hashCode();
     return result;
}

(You'll need more code if any of these could be null)


Pretty much all IDEs will automatically generate both equals() and hashcode(), using all the fields in the class. They'll use something very similar to Bloch's recommendations. Hunt around the UI. You'll find it.

Another alternative is to use Apache ReflectionUtils, which allows you to simply use:

@Override
public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(this);
}

@Override
public boolean equals(final Object obj) {
    return EqualsBuilder.reflectionEquals(this, obj);
}

This works out which fields to use at runtime, and applies Bloch's methods.

like image 29
slim Avatar answered Oct 14 '22 09:10

slim