Java equals() and hashCode() methods are present in Object class. So every java class gets the default implementation of equals() and hashCode(). In this post we will look into java equals() and hashCode() methods in detail.
Java equals()
Object class defined equals() method like this:
public boolean equals(Object obj) {
return (this == obj);
}
According to java documentation of equals() method, any implementation should adhere to following principles.
- For any object x,
x.equals(x)
should returntrue
. - For any two object x and y,
x.equals(y)
should returntrue
if and only ify.equals(x)
returnstrue
. - For multiple objects x, y, and z, if
x.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
should returntrue
. - Multiple invocations of
x.equals(y)
should return same result, unless any of the object properties is modified that is being used in theequals()
method implementation. - Object class equals() method implementation returns
true
only when both the references are pointing to same object.
Java hashCode()
Java Object hashCode() is a native method and returns the integer hash code value of the object. The general contract of hashCode() method is:
- Multiple invocations of hashCode() should return the same integer value, unless the object property is modified that is being used in the equals() method.
- An object hash code value can change in multiple executions of the same application.
- If two objects are equal according to equals() method, then their hash code must be same.
- If two objects are unequal according to equals() method, their hash code are not required to be different. Their hash code value may or may-not be equal.
Importance of equals() and hashCode() method
Java hashCode() and equals() method are used in Hash table based implementations in java for storing and retrieving data. I have explained it in detail at How HashMap works in java? The implementation of equals() and hashCode() should follow these rules.
- If
o1.equals(o2)
, theno1.hashCode() == o2.hashCode()
should always betrue
. - If
o1.hashCode() == o2.hashCode
is true, it doesn’t mean thato1.equals(o2)
will betrue
.
When to override equals() and hashCode() methods?
When we override equals() method, it’s almost necessary to override the hashCode() method too so that their contract is not violated by our implementation. Note that your program will not throw any exceptions if the equals() and hashCode() contract is violated, if you are not planning to use the class as Hash table key, then it will not create any problem. If you are planning to use a class as Hash table key, then it’s must to override both equals() and hashCode() methods. Let’s see what happens when we rely on default implementation of equals() and hashCode() methods and use a custom class as HashMap key.
package com.journaldev.java;
public class DataKey {
private String name;
private int id;
// getter and setter methods
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.journaldev.java;
import java.util.HashMap;
import java.util.Map;
public class HashingTest {
public static void main(String[] args) {
Map<DataKey, Integer> hm = getAllData();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
Integer value = hm.get(dk);
System.out.println(value);
}
private static Map<DataKey, Integer> getAllData() {
Map<DataKey, Integer> hm = new HashMap<>();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
hm.put(dk, 10);
return hm;
}
}
When we run above program, it will print null
. It’s because Object hashCode() method is used to find the bucket to look for the key. Since we don’t have access to the HashMap keys and we are creating the key again to retrieve the data, you will notice that hash code values of both the objects are different and hence value is not found.
Implementing equals() and hashCode() method
We can define our own equals() and hashCode() method implementation but if we don’t implement them carefully, it can have weird issues at runtime. Luckily most of the IDE these days provide ways to implement them automatically and if needed we can change them according to our requirement. We can use Eclipse to auto generate equals() and hashCode() methods. Here is the auto generated equals() and hashCode() method implementations.
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DataKey other = (DataKey) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
Notice that both equals() and hashCode() methods are using same fields for the calculations, so that their contract remains valid. If you will run the test program again, we will get the object from map and program will print 10. We can also use Project Lombok to auto generate equals and hashCode method implementations.
What is Hash Collision
In very simple terms, Java Hash table implementations uses following logic for get and put operations.
- First identify the “Bucket” to use using the “key” hash code.
- If there are no objects present in the bucket with same hash code, then add the object for put operation and return null for get operation.
- If there are other objects in the bucket with same hash code, then “key” equals method comes into play.
- If equals() return true and it’s a put operation, then object value is overridden.
- If equals() return false and it’s a put operation, then new entry is added to the bucket.
- If equals() return true and it’s a get operation, then object value is returned.
- If equals() return false and it’s a get operation, then null is returned.
Below image shows a bucket items of HashMap and how their equals() and hashCode() are related. The phenomenon when two keys have same hash code is called hash collision. If hashCode() method is not implemented properly, there will be higher number of hash collision and map entries will not be properly distributed causing slowness in the get and put operations. This is the reason for prime number usage in generating hash code so that map entries are properly distributed across all the buckets.
What if we don’t implement both hashCode() and equals()?
We have already seen above that if hashCode() is not implemented, we won’t be able to retrieve the value because HashMap use hash code to find the bucket to look for the entry. If we only use hashCode() and don’t implement equals() then also value will be not retrieved because equals() method will return false.
Best Practices for implementing equals() and hashCode() method
- Use same properties in both equals() and hashCode() method implementations, so that their contract doesn’t violate when any properties is updated.
- It’s better to use immutable objects as Hash table key so that we can cache the hash code rather than calculating it on every call. That’s why String is a good candidate for Hash table key because it’s immutable and cache the hash code value.
- Implement hashCode() method so that least number of hash collision occurs and entries are evenly distributed across all the buckets.
You can download the complete code from our GitHub Repository.
Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode