Java Interview Questions (Series) - Equals & HashCode

You can't have one without the other! ♊️

Brief

We continue the Java Interview Questions series with another common one:
What is the contract between the equals and hashcode methods?

In this post we'll talk about both methods and go over:

🔸  What are they used for?

🔸  How they work by default?

🔸  Rules for custom implementations.

🔸  How you can create your own custom implementation?

Implementation

equals and hashCode are overridable methods found on the Object class.

public boolean equals(Object obj)

public native int hashCode()

🔸 equals is used for comparing two objects

🔸 hashCode is used to get an identifier for an object.  You might find it described in other places as unique identifier but we'll see why that's not always the case.

If not provided with a custom implementation, they will fall back on their default logic.


🔵  equals

🔷  Default implementation

The default logic for the equals method is quite straight-forward.

It's only checking to see if the instances of the objects compared are the same.

 public boolean equals(Object obj) {
        return (this == obj);
    }
🔷  Custom implementation

Taking a look on the Javadoc for this method, we understand that equals has a few rules that custom implementations should adhere to.

Say we have three non-null objects: x, y and z. Your custom equals implementation should be:

1️⃣    reflexive
       This means that the result of comparing the object with itself should always be true. x.equals(x) == true

2️⃣    symmetric
       The result of x.equals(y) and y.equals(x) should always be the same.

3️⃣    transitive
       If x.equals(y) == true and y.equals(z) == true, then x.equals(z) should also be true.

4️⃣    idempotent
       Given that nothing changes on the two objects, the result of x.equals(y) should remain the same regardless of how many times the method is invoked.

5️⃣    consistent with null
       If x is not null and y is null, the result of x.equals(y) should always be false.

Let's see in real life, what a custom implementation of equals will look like.

Say we have a Dog class without a custom implementation for the equals method.

public class Dog {
    private Integer age;
    private String name;
    
    public Dog(Integer age, String name) {
        this.age = age;
        this.name = name;
    }
}
Dog dog1 = new Dog(1, "Rex");
Dog dog2 = new Dog(1, "Rex");

dog1.equals(dog2);    // false -> because it's only checking for the two to be the same instance

Adding the custom equals method.

@Override
public boolean equals(Object other) {
    if (other == this) return true; // check if it's the same instance
    if (other == null) return false; // check if the other object is null
    if (!(other instanceof Dog)) return false; // because the input variable is Object, we need to check if it's actually a Dog we're comparing to
    Dog otherDog = (Dog)other;
    return this.age == otherDog.age && this.name.equals(otherDog.name); // actual value comparing
}
Dog dog1 = new Dog(1, "Rex");
Dog dog2 = new Dog(1, "Rex");

dog1.equals(dog2);    // true 


🔵  hashcode

🔷  Default implementation

The default implementation for hashCode is a lot more tricky as it's a native method implemented different depending on the JVM.

You can think of the default hashcode implementation as a function of the object's address casted to an Integer.

❗️ But that's not always the case!

🔷  Custom implementation

Similar to equals, custom implementations of hashCode should follow some guidelines:

1️⃣    idempotent
       Given that nothing changes on the object, the result of x.hashCode() should remain the same regardless of how many times the method is invoked.

2️⃣    consistency around equals
       If x.equals(y) == true, the result of x.hashCode() and y.hashCode() should also be equal.

       For this reason, you shouldn't add a custom implementation for equals() without providing one for hashCode() and vice-versa.

       On the other hand, if x.equals(y) == false, the result of x.hashCode() and y.hashCode() can still be equal. This may affect performance around HashMaps and HashTables, and that's why you should strive for a good hashCode() implementation.

🔸   A common way to manually calculate the hashCode of an object is to start from a prime random number and to continue adding or multiplying the hashcodes of its fields.

This way, you'll always get the same result for the same object.

@Override
public int hashCode() {
    int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + name.hashCode();
    return result;
}

💡
Don't miss out on more posts like this! Susbcribe to our free newsletter!
💡
Currently I am working on a Java Interview e-book designed to successfully get you through any Java technical interview you may take.
Stay tuned! 🚀