Java Object class provides basic implementation of methods – hashCode() and equals(). These methods are extremely useful especially when working with the Collection framework. The hash table implementations rely on these methods for storing and retrieving data.
In this tutorial, we’ll learn about the contract between hashCode() and equals(), their default implementations. We’ll also talk about when and how to override these methods.
Let’s first look at the default implementations of these methods:
The equals() method present in the Object class simply compares the object references:
public boolean equals(Object obj) { return (this == obj); }
So, by default, obj1.equals(obj2) is same as obj1 == obj2.
The equals() method compares actual values for classes like String etc as it’s overridden in those respective classes.
The hashCode() method’s signature in the JDK is:
public native int hashCode();
Here, the native keyword indicates that the method is implemented in native code using JNI (Java Native Interface).
The hashCode() method returns an int type. The returned value, by default, represents the object memory address.
Before we override the equals() and hashCode() methods, let’s first look at the guidelines:
1. equals(): Our implementation of equals() method must be:
2. hashCode(): When implementing hashCode(), we must consider the following points:
Although all the above principles are to be kept in mind while overriding these methods, there’s one popular rule among these:
For the two objects obj1 and obj2,
- If obj1.equals(obj2) then obj1.hashCode() = obj2.hashCode() must hold true
- However, if obj1.hashCode() == obj2.hashCode(), then obj1.equals(obj2) can either return true or false i.e. obj1 and obj2 might or might not be equal
This is popularly known as the equals() and hashCode() contract.
The hashCode() and equals() method play an important role in storing and retrieving elements in a hash table based implementation. The hashCode() determines the bucket the given item maps to. Within a bucket, the equals() method is used to look for the given entry.
Let’s say we have an Employee class:
public class Employee { private int id; private String name; //constructors, getters, setters, toString implementations }
And a HashMap storing Employee as the keys:
Map<Employee, Integer> map = new HashMap<>(); map.put(new Employee(1, "Sam"), 1); map.put(new Employee(2, "Sierra"), 2);
Now that we have inserted two entries, let’s try a containsKey() check:
boolean containsSam = map.containsKey(new Employee(1, "Sam")); //false
Although we have an entry for Sam, containsKey() returned false. That’s because we haven’t yet overridden the equals() and hashCode() methods. And by default, equals() will simply do a reference-based comparison.
As per Javadocs:
When we override equals() method, we must also override the hashCode() method
This will help to avoid breaking the equals-hashCode contract.
Note that the compiler won’t complain if we break the contract, but we might end up facing unexpected behaviors say when we store such objects as keys in a HashMap.
We can quickly override these methods using the feature of an IDE. When using Eclipse, we can go to Source->Generate hashCode() and equals(). Let’s look at the generated implementations for our Employee class:
public class Employee { ... @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Employee other = (Employee) obj; if (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if(!name.equals(other.name)) return false; return true; } }
Clearly, the same fields have been used for implementing both equals() and hashCode() methods to keep up with the contract.
Some of the best practices to follow when working with equals() and hashCode() includes:
In this tutorial, we first looked at the default implementations of equals() and hashCode() methods. Later, we discussed when and how to override these methods.