In Java, all the class extends from the Object class and so inheriting the equals and hashCode method
The equals method is used to determine whether 2 objects are the same, while hashCode is used to determine which bucket to put the object if hashing structure is used
If the equals method is changed, the hashCode method has to be changed as well to make sure all the objects that are equal returns the same value in hashcode
Otherwise, these objects will end up at different buckets and the equals method will not work properly
Sometimes, the default implementation of equals is not good so we need to override it.
Let say we have a Cube class with 2 fields:
public class Cube { int edge; String color; public Cube(int edge, String color) { this.edge = edge; this.color = color; } }
In the main class, we store some Cubes in a HashMap to its count, then try to get the count by using a new Cube with the exact edge and color:
public class MainClass { static Map<Cube, Integer> cubeMap = null; public static void main(String[] args) { cubeMap = new HashMap<>(); cubeMap.put(new Cube(10, "Red"), 2); cubeMap.put(new Cube(12, "Red"), 3); cubeMap.put(new Cube(12, "Yellow"), 8); cubeMap.put(new Cube(14, "Red"), 7); cubeMap.put(new Cube(14, "Blue"), 5); Integer count = cubeMap.get(new Cube(12, "Yellow")); System.out.println("Count = " + count); } }
Now if we try to get the count of Cube with edge = 12 and color = "Yellow", by creating a Cube with these values and get it from the map, nothing will be found
Output:
Count = null
The reason why we could not create a new Cube by using the same fields as the key is, when the HashMap looks for whether the key matches, it uses the equals method of the object
In this case the equals method is simply comparing the reference, which is different since the object stored in the HashMap and object passed to the get method are completely 2 different objects.
To solve this problem, what we can do is to override the equals method, making the Cube objects equal when both the edge and color are the same:
public class Cube { int edge; String color; public Cube(int edge, String color) { this.edge = edge; this.color = color; } public boolean equals(Object obj) { if (obj instanceof Cube) { Cube cube = (Cube)obj; if (cube.color.equals(this.color) && cube.edge == this.edge) { return true; } else { return false; } } else { return false; } } }
Now run it again, but still does not work, the output is still:
Output:
Count = null
The reason is because the 2 objects, even though they are equal, they still have different hash codes, which will make them on different buckets on the hashmap
(so even though under comparison they will be equal but they don't event got a chance to be compared since they are on different buckets)
What we have to do is to override hashCode to make sure equal Cubes return the same value of hashCode:
public class Cube { int edge; String color; public Cube(int edge, String color) { this.edge = edge; this.color = color; } public boolean equals(Object obj) { if (obj instanceof Cube) { Cube cube = (Cube)obj; if (cube.color.equals(this.color) && cube.edge == this.edge) { return true; } else { return false; } } else { return false; } } @Override public int hashCode() { int hashCode = 0; for (int i = 0; i < color.length(); i++) { hashCode += Math.pow(7, i) * color.charAt(i); } hashCode += Math.pow(7, color.length()) * edge; return hashCode; } }
Note that we need to make the hashcode as unique as possible, so that the objects could be scattered around on the hashmap
Remember no need to make a hashcode absolutely unique since it is ok for some objects end up in the same bucket
Now run it again, but time it will show the correct count:
Output:
Count = 8