Sunday 28 February 2016

Understanding hashCode and equals contract

When it comes to storing objects in a hashMap, we must follow the contract between hashCode() and equals(). The contract says:

  1. If two objects are equal, then they must have the same hash code.
  2. If two objects have the same hash code, they may or may not be equal.
or in other words
If equal, then same hash codes too.
Same hash codes no guarantee of being equal.

Lets see the scenarios what happens when we deal with object in hashMap:
import java.util.HashMap;

// Scenario 1 : Neither of  equals() or hashCode() is overridden 
import java.util.*;
 class Apple {
private String color;
public Apple(String color) {
this.color = color;
}
  public static void main(String[] args) {
Apple a1 = new Apple("green");
Apple a2 = new Apple("red");
//hashMap stores apple type and its quantity
HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Apple("green")));
}
}

The out put is simply "null" (of course which is not expected)
The reason is when we are calling m.get(new Apple("green")), the hash created by hashCode() would be different as its will hold a different memory location. So it will not be available in the hash table bucket.

// Scenario 2 : Overriding equals() 
import java.util.*;
 class Apple {
private String color;
public Apple(String color) {
this.color = color;
}
      //overriding equals
public boolean equals(Object obj) {
if(obj==null) return false;
if (!(obj instanceof Apple))
return false;
if (obj == this)
return true;
return this.color.equals(((Apple) obj).color);
}

  public static void main(String[] args) {
Apple a1 = new Apple("green");
Apple a2 = new Apple("red");
//hashMap stores apple type and its quantity
HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Apple("green")));
}
}

Still the out put is  "null" (again which is not expected)
Reason: Here we broke the contract of hashCode() and equals(). We must override hashCode() when we override equals(). Because the default behavior of equals() is to compare memory of the objects. When we are overriding the behavior of equals this will change the default behaviour..which leads to breaking the contract between hashCode() and equals(){If two objects are equal, then they must have the same hash code.}

// Scenario 3 : Overriding both equals() and hashCode() {Correct scenario}

import java.util.*;
 class Apple {
private String color;
public Apple(String color) {
this.color = color;
}
      //overriding equals
public boolean equals(Object obj) {
if(obj==null) return false;
if (!(obj instanceof Apple))
return false;
if (obj == this)
return true;
return this.color.equals(((Apple) obj).color);
}
     //overriding hashCode
public int hashCode(){
return this.color.hashCode();
}

  public static void main(String[] args) {
Apple a1 = new Apple("green");
Apple a2 = new Apple("red");
//hashMap stores apple type and its quantity
HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();
m.put(a1, 10);
m.put(a2, 20);
System.out.println(m.get(new Apple("green")));
}
}

Now hope you hape a better understanding why we should override hashCode() and equals() while dealing with hashMap and objects.
References: