The object equals contract indicates that when two objects are equal, their hash codes must also be the same. It’s a general agreement for all Java objects used in hash-based collections. Its main purpose is to optimize performance when working e.g. with HashMap or HashSet.
You may hear that when you implement the equals() method for your class, you should also implement the hashCode() method. That’s the practical approach for fulfilling the equals contract.
If you want to know more details why the contract is so important, keep on reading.
Advertisement
In short, to avoid possible bugs in the future.
If you create a class and implement only one of these methods, your code will compile and technically execute without a problem. But only technically. From the logical point of view, you may run into hard to find runtime bugs.
Imagine you created a Player class which had a single String field for player’s name. Because the logic in your application required to compare players, you decided to override the equals() method.
Player p1 = new Player("John"); Player p2 = new Player("John"); System.out.println("Same players: " + p1.equals(p2)); // prints: Same players true
Next, your teammate used the Player class in a HashSet without checking how your class was implemented and if you fulfilled the object equals contract. Believe me or not but it’s a common mistake.
Set<Player> uniquePlayers = new HashSet<>(); uniquePlayers.add(new Player("John")); uniquePlayers.add(new Player("John")); System.out.println("Unique players " + uniquePlayers.size()); // prints: Unique players 2
As you know, sets don’t allow to put duplicated in the collection. So why did it allow to put two equal objects in the above example?
The answer is performance optimization.
Usually, it’s much faster to calculate and compare hashes of two objects rather than running the full equals() method. That’s why collections first check equality of hash codes and use the equals() method only as a fallback mechanism.
The object equals contract says that if two objects are equals, their hash codes are also equal. The contract implicates that if hash codes of two objects aren’t equal, these objects aren’t equal as well.
It’s pretty obvious once you think about it. But then, a new question may arise in your head…
As I already mentioned, collections use the equals() method as a fallback mechanism. They do that because it’s possible and totally acceptable (but not recommended) that two unequal objects return the same hash codes.
Let’s return to the Player class and consider again the HashSet sample but this time with the following implementation of the hashCode() method:
class Player { //... @Override public int hashCode() { return 4; } }
Not the best possible implementation under the sun but it actually fulfills the object equals contract. Please don’t reuse this code in your production applications 🙂
Surprisingly, this poor implementation makes the HashSet from the previous example works as we expect.
Set<Player> uniquePlayers = new HashSet<>(); uniquePlayers.add(new Player("John")); uniquePlayers.add(new Player("John")); System.out.println("Unique players " + uniquePlayers.size()); // prints: Unique players 1
Why does it work now as we expect? Let’s see what’s going on under the hood of the HashSet:
Now, let’s consider the opposite situation in which the Player class has only hashCode() implemented and equals() is missing.
Set<Player> uniquePlayers = new HashSet<>(); uniquePlayers.add(new Player("John")); uniquePlayers.add(new Player("John")); System.out.println("Unique players " + uniquePlayers.size()); // prints: Unique players 2
Hash codes for both players are again the same so the set needs to compare objects using the equals() method. Because you don’t override it, the default implementation was used and the result was false. Eventually, both players are added to the set.
You may hear a false opinion that every class should implement equals() and hashCode() methods. However, in practice, such an approach doesn’t really make sense and it’s counterproductive.
Think about some utility or glue code layer classes. Usually, you have only one instance of such classes in your application. You’ll never compare it with another instance.
You should override the equals() method only when the Java class:
If your class meets any point from the above list, go ahead and write the equals() method for it.
Even if you don’t plan to compare instances of a new class you create, I advise you to implement equals() and hashCode() method to avoid mistakes in the future. Sooner or later you’ll need them.
At this point, you should know why it’s important to always override equals() and hashCode() together. Writing implementation for these methods should be your routine but definitely not for every class you create. As we discussed, it makes sense only for certain types.
Please let me know in the comment if the article is helpful and easy enough to follow. It’ll let me know what I should improve in my writing. Also, subscribe to my blog so you won’t miss another post which you might find useful.
Short answer: No.Fortunately, you can simulate them.Many programming languages like C++ or modern JavaScript have…
The JavaScript Promise is a concept that every modern self-respecting web developer should be familiar…
In short, a Spring bean is an object which Spring framework manages at runtime. A…
Have you ever wonder why singleton is the default scope for Spring beans? Why isn't…
Do you have multiple parameters annotated with @RequestParam in a request mapping method and feel…
Some teams prefer having a separate Maven build profile for each application runtime environment, like…