Working with hashcode() and equals() in java

By default, the java super class java.lang.Object provides 2 important methods: equals() and hashcode() for comparing objects, these methods become very useful when implementing large business which requires interactions between several classes. In this article we talk about the relation between these methods, their default implementation and the circumstances which force developers to provide a custom implementation for each of them.

1. Method definition & default implementation

equals(Object obj): it is a method provided by java.lang.Object which indicates whether some other object passed as argument is “equal to” the current instance. The default implementation provided by the jdk is based on the memory location, so that 2 objects are equal if and only if they are stored in the same memory address.

hashcode(): it is a method provided by java.lang.Object which returns an integer representation of the object memory address. By default, this method returns a random integer which is unique for each instance, this integer might change between several executions of the application and wouldn’t stay the same.

2. Contract between equals() and hashcode()

The default implementation is not enough to satisfy business needs, especially if we’re talking about a huge application which considers 2 objects as equal when some business fact happens. In some business scenarios, developers provide their own implementation in order to force their own equality mechanism regardless the memory addresses.

As per java documentation, developers should override both methods in order to achieve a fully working equality mechanism and it’s not enough to just implement the equals() method.

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.

In the following sections, we provide several examples which show the importance of overriding both methods and the drawbacks of overriding equals() without hashcode().

3. Practical example

We define a class called Student as the following:

For testing purposes, we define a main class HashcodeEquals which checks whether 2 instances of Student (who has the exact same attributes) are considered as equal.

Output: 

Although the two instances have exactly the same attribute values, they are stored in different memory locations. Hence they are not considered equal as per the default implementation of equals(). The same applies for hashcode(),  a random unique code is generated for each instance.

4. Overriding equals()

For business purposes, we consider that 2 students are equal if they have the same ID, so we override equals() method and provide our own implementation as the following:

In the above implementation, we are saying that 2 students are equal if and only if they are stored in the same memory address OR they have the same ID. Now if we try to run HashcodeEquals we will get the following output:

As you noticed, overriding equals() with our custom business forces java to consider the ID attribute when comparing 2 Student objects.

equals() with ArrayList

A very popular usage of equals()  is defining an array list of Student and searching for a particular student inside it. So we modified our testing class in order the achieve this.

After running the above test, we get the following output:

When calling studentsLst.contains(new Student(1,”Alex”)) , the list compares the passed object with all of its elements using the equals() method and since we already have a student with (id = 1) then it will return true, in case we didn’t override equals()  the list will consider the passed argument as a totally new object and will return false.

5. Overriding hashcode()

Okay so we override equals() and we get the expected behavior even though the hash code of the 2 objects are different, so what’s the purpose of overriding hashcode()?

equals() with HashSet

Let’s consider a new test scenario, we want to store all the students in a HashSet, so we update HashcodeEquals as the following:

If we run the above test, we get the following output:

WAIT !! we already override equals() and verified that alex1 and alex2 are equal, and we all know that HashSet stores unique objects, so why did it consider them as different objects ?

HashSet stores its elements in memory buckets, each bucket is linked to a particular hash code. When calling students.add(alex1), java stores alex1 inside a bucket and links it to the value of alex1.hashcode(). Now any time an element with the same hash code is inserted into the set, it will just replace alex1. However, since alex2 has a different hash code then it will be stored in a separate bucket and will be considered a totally different object.

Now when HashSet search for an element inside it, it first generates the element’s hash code and looks for a bucket which corresponds to this hash code.

Here comes the importance of overriding hashcode(), so let’s override it in Student and set it to be equal to the ID, so that students who have the same ID are stored in the same bucket:

Now if we try to run the same test, we get the following output:

See the magic of hashcode() !! the 2 elements are now considered as equal and stored in the same memory bucket, so any time you call contains() and pass a student object holding the same hash code, the set will be able to find the element.

The same is applied for HashMap, HashTable and any data structure which uses hashing mechanism for storing elements.

6. Conclusion

In order to achieve a fully working custom equality mechanism, it is mandatory to override hashcode() each time you override equals(). Follow the below tips and you’ll never have leaks in your custom equality mechanism:

  • If 2 objects are equal, they MUST have the same hash code.
  • If 2 objects have the same hash code, it doesn’t mean that they are equal.
  • Overriding equals() alone will make your business fail with hashing data structures like: HashSet, HashMap, HashTable … etc.
  • Overriding hashcode() alone doesn’t force java to ignore memory addresses when comparing 2 objects.

 

husseinterek

Founder of programmergate.com, I have a passion in software engineering and everything related to java environment.

You may also like...

2
Leave a Reply

avatar
1 Comment threads
1 Thread replies
1 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
husseinterekJoan K Recent comment authors
newest oldest most voted
Joan K
Guest
Joan K

I like your articles very much. I have just read 3…! You pare it right down to the basic point and keep the articles short and simple.
And the articles present beautifully on my Android phone which is very useful for reading in the train.
I will look out for your articles in future!