Why We Should override hashCode method when we override equals method

Hello Friends,

In one of my previous post, I wrote about Why Should we override equals method in Java.In this article, let us try to find out Why overriding hashcode method is important when we override equals method.



So in the post Why Should we override equals method in Java, we saw that we override equals method when we want two different objects in memory to be functionally equal.I will strongly recommend going through my previous post for more details before reading this post.

Here is hashCode contract from java docs :
  • 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
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables
SoftwareDeveloper Class with only equals() method overridden without overriding hashcode()

I have modified SoftwareDeveloper class in this example a little bit to simplify things with only two properties name and totalExperience.
package com.blogspot.javasolutionsguide;
  
class SoftwareDeveloper {

 private String name;
 private String totalExperience;

 public SoftwareDeveloper(String name, String totalExperience){
  this.name = name;
  this.totalExperience = totalExperience;
 }

 public String getName() {
  return name;
 }

 public String getTotalExperience() {
  return totalExperience;
 }
 
 public void work() {
  System.out.println("Keep Working");
 }

 public void eat() {
  System.out.println("Eat occasionally");
 }

 public void sleep() {
  System.out.println("Sleep occasionally");
 }

@Override
public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj == null) {
   return false;
  }
  if (getClass() != obj.getClass()) {
   return false;
  }
  SoftwareDeveloper other = (SoftwareDeveloper) obj;
  if (totalExperience == null) {
   if (other.totalExperience != null)
    return false;
  } else if (!totalExperience.equals(other.totalExperience)) {
   return false;
  }
  return true;
 }
}
Test Class :
package com.blogspot.javasolutionsguide;

public class TestClass {

    public static void main(String[] args) {
        SoftwareDeveloper softwareDeveloper1 = new SoftwareDeveloper("Gaurav", "9");
        SoftwareDeveloper softwareDeveloper2 = new SoftwareDeveloper("Gaurav", "9");
        System.out.println("Software Developer1 HashCode :" + softwareDeveloper1.hashCode());
        System.out.println("Software Developer2 HashCode :" + softwareDeveloper2.hashCode());

    }
}

Here I am instantiating two objects of SoftwareDeveloper which are functionally equal.Please note that we have overridden only equals method in SoftwareDeveloper class and not HashCode method yet.In the main method, we are printing HashCodes of both SoftwareDeveloper 1 and SoftwareDeveloper 2. 

Output:
Software Developer1 HashCode: 2018699554
Software Developer2 HashCode: 1311053135
As we can see from above output, even if the two objects are equal as per equals method, but hashcode of these two objects is coming differently.

Question, where is this hasCode() method, as we have not defined it in SoftwareDeveloper Class.

The answer, I think you already know, that definition of above hashCode() method is in the Object class, which is the parent of all classes in Java and hashcode() implementation of Object class returns different hashcodes for different objects in memory.

But one may ask what is the problem in that, even if for two equal objects as per equal method, different hashcodes are being returned.

Problem is that, when we use such objects which we consider as equal as per our equals method implementation in hash-based collections such as HashMap, HashSet etc. it causes unexpected behavior. The reason is that these hash-based collections use hashcodes of the object in their implementation and those implementations expect that all objects which are equal as per equals method will be having same hashcodes.

What are the Consequences of not overriding hashcode method and overriding only equals method

Example 1 :

What we are going to do 

1. Put one object of SoftwareDeveloper Class as key in HashMap and "SoftwareDeveloper1" as value.
2. Try to retrieve the value by putting a new object(in memory) which is functionally equivalent to
an object which we put earlier as key.
3. First override only equals method and see result.
4. Then override hashCode method and see result.

Creating Software class with only equals() method overriden and using it' object as key of HashMap

package com.blogspot.javasolutionsguide;

class SoftwareDeveloper {

    private String name;
    private String totalExperience;

    public SoftwareDeveloper(String name, String totalExperience) {
        this.name = name;
        this.totalExperience = totalExperience;
    }

    public String getName() {
        return name;
    }

    public String getTotalExperience() {
        return totalExperience;
    }

    public void work() {
        System.out.println("Keep Working");
    }

    public void eat() {
        System.out.println("Eat occasionally");
    }

    public void sleep() {
        System.out.println("Sleep occasionally");
    }

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

TestClass :
package com.blogspot.javasolutionsguide;

public class TestClass {

    public static void main(String[] args) {
        Map<Object, String> developersMap = new HashMap<Object, String>(); \\Line 1
        SoftwareDeveloper softwareDeveloper1 =
                new SoftwareDeveloper("Gaurav", "9");   \\Line 2
        developersMap.put(softwareDeveloper1, "SoftwareDeveloper1");     \\Line 3
        System.out.println("Value corresponding to Software developer 1 equivalent object:" + developersMap.get(new SoftwareDeveloper("Gaurav", "9"))); \\
        Line 4
    }
}

So What we are doing in above three lines of code :

Line 1 : We are creating a Map which can take any object as Key and String as Value.
Line 2 : We are creating a Software Developer object with parameters "Gaurav" and "9".
Line 3 :we are putting SoftwareDeveloper1 object created in step 2, as key and "SoftwareDeveloper1" as value.
Line 3 : We are trying to get value corresponding to object that we just added in Line 3, but instead of
using same reference, we are trying to get it via new Object which is equal to softwareDeveloper1 functionally(as they have the same name and experience).

Output :
Value corresponding to Software developer 1 equivalent object: null

Why we got null, even when we tried to get back value using softwareDeveloper object which was equal to softwareDeveloper1 object functionally as per equal method.This is because when putting and retrieving entries in HashMap, HashMap makes use of hashcode method of the key and it expects hashCode to be same for objects which are equal as per equal method.

Note: Please read how Hashmap works internally in java to find more details on how HashMap's get and put methods work internally and make use of hashCode.

Creating Software class with both equals() and hashCode() methods overriden and using it's object as key of HashMap

Now, Let us override hashCode() method as well and see result.So below is the enhanced version of SoftwareDeveloper class with overriden hashCode method.
package com.blogspot.javasolutionsguide;                                                                                                                                                                           public class SoftwareDeveloper {

    private String name;
    private String totalExperience;

    public SoftwareDeveloper(String name, String totalExperience){
        this.name = name;
        this.totalExperience = totalExperience;
    }

    public String getName() {
        return name;
    }

    public String getTotalExperience() {
        return totalExperience;
    }

    public void work() {
        System.out.println("Keep Working");
    }

    public void eat() {
        System.out.println("Eat occasionally");
    }

    public void sleep() {
        System.out.println("Sleep occasionally");
    }

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

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

Test Class :
package com.blogspot.javasolutionsguide;

public class TestClass {

    public static void main(String[] args) {

        SoftwareDeveloper softwareDeveloper1 = new SoftwareDeveloper("Gaurav", "9");
        Map<Object, String> developersMap = new HashMap<Object, String>();
        developersMap.put(softwareDeveloper1, "SoftwareDeveloper1");
        System.out.println("Value corresponding to Software developer 1 equivalent object:" + developersMap.get(new SoftwareDeveloper("Gaurav", "9")));
    }
}

Output :
Value corresponding to Software developer 1 equivalent object: SoftwareDeveloper1

So as evident from output, when we overrode hashCode method, we were able to retrieve value corresponding to softwareDeveloper1 object from hashMap, by using new object which was equivalent to softwareDeveloper1 object functionally as per equals method.
  

Example 2 :

What we are going to do

1. Put one object of SoftwareDeveloper class in HashSet.
2. Create another object(in memory) which is functionally equivalent to first object and try putting this in HashSet.
3. Try first without overriding hashCode() method and then with overriding hashCode method.

Note : I would suggest to go through my post on How Hashset Works Internally in Java before going through below examples.

Creating Software class with only equals() method overriden and adding two functionally equal objects in HashSet

Note: Use same SoftwareDeveloper class from Example 1 with only equals() method overriden and without hashCode() method overriden.

TestClass :
public class TestClass {

    public static void main(String[] args) {
        SoftwareDeveloper softwareDeveloper1 = new SoftwareDeveloper("Gaurav", "9");

        Set<Object> set = new HashSet<Object>();
        set.add(softwareDeveloper1);
        System.out.println("Size of Set Before is :" + set.size());

        set.add(new SoftwareDeveloper("Gaurav", "9"));
        System.out.println("Size of Set After is :" + set.size());
    }
}

Output :
Size of Set Before is :1
Size of Set After is :2

We know very well that HashSet does not allow duplicates, but if we see above example, then it is clear that two equal objects as per equal method are added in Set.This is because we have not overriden hashCode() method and HashSet being Hash based collection uses hashCode of object to add object in HashSet.

Creating Software class with both equals() and hashCode() methods overriden and adding two functionally equal objects in HashSet
Note : Take SoftwareDeveloper Class's version with overriden hashCode method from Example 1.

Now if we execute above TestClass, we will have following output :

Output :
Size of Set Before is :1
Size of Set After is :1

So from the output, it is evident that when we overrode hashcode() method, HashSet behaved as expected and duplicates were not added.
So with this we learnt Why We Should override hashCode() method when we override equals method.

Thanks for reading.

Share it with someone , to whom you think this post will be helpful.