What is Java object equals contract?

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

Why we must override equals() and hashCode() together

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.

Some bug are hard do find

What happens when we implement only equals()

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, they 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…

Are objects equal if their hash codes are the same?

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:

  1. We create an empty HashSet instance.
  2. We put the first player instance inside the set. Because the collection is empty, it doesn’t have to check if the new element is a duplicate.
  3. Next, we try adding the second player which is the same as the first one.
  4. The set calculates the hash code of the second player and checks if it already has some elements with such hash code.
  5. Because there is an element with the same hash code, the set needs to make sure if it’s just a hash code collision or if objects are really the same using the equals() method.
  6. Since the objects with the same hash codes are also equal, the set rejects the second player instance and doesn’t add it to the collection.

What happens when we implement only hashCode()

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.

When we should implement equals() and hashCode()

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:

  • represents some data,
  • represents a state,
  • is a value object,
  • is going to be used in collections.

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.

Conclusion

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.

Facebooktwittergoogle_plusredditlinkedinmail

Articles you may like

Advertisement