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;
}
Stay tuned! 🚀