Why Should I Override Hashcode() When I Override Equals() Method

Why do I need to override the equals and hashCode methods in Java?

Joshua Bloch says on Effective Java

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.

Let's try to understand it with an example of what would happen if we override equals() without overriding hashCode() and attempt to use a Map.

Say we have a class like this and that two objects of MyClass are equal if their importantField is equal (with hashCode() and equals() generated by eclipse)

public class MyClass {
private final String importantField;
private final String anotherField;

public MyClass(final String equalField, final String anotherField) {
this.importantField = equalField;
this.anotherField = anotherField;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((importantField == null) ? 0 : importantField.hashCode());
return result;
}

@Override
public boolean equals(final Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final MyClass other = (MyClass) obj;
if (importantField == null) {
if (other.importantField != null)
return false;
} else if (!importantField.equals(other.importantField))
return false;
return true;
}
}

Imagine you have this

MyClass first = new MyClass("a","first");
MyClass second = new MyClass("a","second");

Override only equals

If only equals is overriden, then when you call myMap.put(first,someValue) first will hash to some bucket and when you call myMap.put(second,someOtherValue) it will hash to some other bucket (as they have a different hashCode). So, although they are equal, as they don't hash to the same bucket, the map can't realize it and both of them stay in the map.


Although it is not necessary to override equals() if we override hashCode(), let's see what would happen in this particular case where we know that two objects of MyClass are equal if their importantField is equal but we do not override equals().

Override only hashCode

If you only override hashCode then when you call myMap.put(first,someValue) it takes first, calculates its hashCode and stores it in a given bucket. Then when you call myMap.put(second,someOtherValue) it should replace first with second as per the Map Documentation because they are equal (according to the business requirement).

But the problem is that equals was not redefined, so when the map hashes second and iterates through the bucket looking if there is an object k such that second.equals(k) is true it won't find any as second.equals(first) will be false.

Hope it was clear

Why should I override hashCode() when I override equals() method?

It works for you because your code does not use any functionality (HashMap, HashTable) which needs the hashCode() API.

However, you don't know whether your class (presumably not written as a one-off) will be later called in a code that does indeed use its objects as hash key, in which case things will be affected.

As per the documentation for Object class:

The general contract of hashCode is:

  • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

Why is it important to override GetHashCode when Equals method is overridden?

Yes, it is important if your item will be used as a key in a dictionary, or HashSet<T>, etc - since this is used (in the absence of a custom IEqualityComparer<T>) to group items into buckets. If the hash-code for two items does not match, they may never be considered equal (Equals will simply never be called).

The GetHashCode() method should reflect the Equals logic; the rules are:

  • if two things are equal (Equals(...) == true) then they must return the same value for GetHashCode()
  • if the GetHashCode() is equal, it is not necessary for them to be the same; this is a collision, and Equals will be called to see if it is a real equality or not.

In this case, it looks like "return FooId;" is a suitable GetHashCode() implementation. If you are testing multiple properties, it is common to combine them using code like below, to reduce diagonal collisions (i.e. so that new Foo(3,5) has a different hash-code to new Foo(5,3)):

In modern frameworks, the HashCode type has methods to help you create a hashcode from multiple values; on older frameworks, you'd need to go without, so something like:

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;
}

Oh - for convenience, you might also consider providing == and != operators when overriding Equals and GetHashCode.


A demonstration of what happens when you get this wrong is here.

When do you need to override hashcode() and equals() when using a hashmap

The answer is yes.

In Java you can add objects in collections. Let us say you wanted to find an object called A that you added to a list called L. Let us say this is an object that you defined with your own class and you override the method Object#equals(). When you are looping through list L you are testing if any of these objects are equal to object A. If the equals method returns true you have found your object.

When you add objects to any HashTable, HashMap or HashSet the hashcode method is used to generate a number. That number should as unique as possible. It is possible that objects of the same class have different values in their instance fields but their hashcode method produces the same value. If you have two objects X and Y of some class and they have the same hashcode and you put both of them in a HashMap Q they end up in the same bucket P. Let us say that P has two objects. Let say that you pass Q into a method with X and Y. The method wants to check if X exists in Q. Q will take X and get the hashcode. Q will use the hashcode to find a bucket. The bucket will be P. Bucket P has two objects. This is when the equals method is used to determine if the Bucket contains X by comparing each object in the bucket with X. If one of the object in the bucket matches X then X exists in Q.

Should I be overriding equals and hashCode in child classes even if it's not adding anything?

This is a bit complicated; I have to explain a few things about how equals and hashCode works to explain viable solutions.

There is a 'contract'. The compiler cannot enforce it, but if you do not adhere to this contract, weird things will happen. Specifically: Your objects will just do the wrong thing when used as keys in hashmaps, and possibly other such problems when using third party libraries. To properly adhere to the contract, any given class either needs to opt out of equals/hashCode entirely, OR, the entire chain (so, the class and all its subclasses) need to properly override hashCode and equals, except, you really can't do that unless the parent is properly instrumented to do so.

The contract states that this must always be correct:

  • a.equals(b) -> b.equals(a).
  • a.equals(b) and b.equals(c) -> a.equals(c).
  • a.equals(a).
  • a.equals(b) -> a.hashCode() == b.hashCode(). (note, the reverse need not be true; equal hashcodes does not imply the objects are equal).

The contract is REALLY difficult to guarantee in the face of a class hierarchy! Imagine that we take the existing java.util.ArrayList and subclass it with the notion of 'color'. So now we can have a blue ColoredArrayList, or a red ColoredArrayList. It would make perfect sense to say that a blue ColoredArrayList definitely should NOT equal a red ColoredArrayList, except.. the equals impl of ArrayList itself (which you cannot change), effectively defines that you simply cannot extend ArrayList with properties like this at all: if you call a.equals(b) where a is an empty arraylist and b is some empty List (say, an empty red ColoredArrayList), it'll just check equality of each member in it, which, given that they are both empty, is trivially true. So, the empty normal arraylist is equal to both the empty red and the empty blue ColoredArrayList, and therefore the contract stipulated that an empty red ColoredArrayList must equal an empty blue ColoredArrayList. In that sense, sonar is just broken here. There's a problem, and it is unfixable. It is impossible to write the concept of ColoredArrayList in java.

Nevertheless, there is a solution, but only if every class in the hierarchy is on board. This is the canEqual approach. The way out of the colored dilemma as above is to differentiate the notion of 'I am extending, and adding new properties' and 'I am extending, but, these things are still semantically speaking the exact same thing with no new properties'. ColoredArrayList is the former case: It's an extension that adds new properties. The idea of canEqual is that you create a separate method to indicate this, which lets ArrayList figure out: I cannot be equal to ANY ColoredArrayList instance, even if all elements are the same. Then we can adhere to the contract again. ArrayList does NOT have this system in place and therefore, given that you cannot change ArrayList's source code, you're stuck: It is not fixable. But if you write your own class hierarchy, you can add it.

Project Lombok takes care of adding equals and hashCode for you. Even if you don't want to use it, you can look at what it generates and duplicate this in your own code. This will also remove the warnings that sonar emits. See https://projectlombok.org/features/EqualsAndHashCode – this also shows you how the canEqual concept can be used to avoid the ColoredArrayList dilemma.

Here you subclass without adding new properties, so, there's no actual need to replace hashCode and equals. But sonar doesn't know that.

Should I always override equals, hashcode and toString methods?

Each of these methods has its own significance. equals and hashCode methods are used for comparison and hashing and toString mainly for logging purposes.
If you don't want any of these functionalities, you are not required to implement them.

When do I need to override equals and hashcode methods?

If I compare 2 instances of A without override the equals method, will I get expected result?

That depends on what you expect :)

The default implementation will give you reference equality - in other words, when you compare two references, equals will only return true if they're references to the same object.

You would normally override equals to implement "value equality" - where two distinct objects are deemed equal, usually by virtue of having equal field values themselves. The exact meaning of equality will depend on your design - the two objects could still be distinguishable in other ways, for example.

If you override equals, you should also override hashCode to be consistent with equals, such that if a.equals(b) is true, then a.hashCode() == b.hashCode(). This will allow instances of your class to be used as keys in hash-based collections (e.g. HashMap) so that you can look up a value based on a key which is equal to the original one, rather than having to use a reference to the exact original key object.



Related Topics



Leave a reply



Submit