Why override equals and hashcode methods in Java?
Source:
Medium This article focuses on two closely related methods: equals() and hashcode() . You'll learn how they interact with each other and how to override them correctly.
Why do we override the equals() method?
In Java, we cannot overload the behavior of operators like
== ,
+= ,
-+ . They work according to a given process. For example, consider the operation of the
== operator .
How does the == operator work?
It checks whether the two references being compared point to the same instance in memory.
The == operator will only evaluate to true if the two references represent the same instance in memory. Let's take a look at the sample code:
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
Let's say in your program you have created two Person objects in different places and want to compare them.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
From a business perspective, the two look the same, right? But for the JVM they are not the same. Since they are both created using the
new keyword , these instances are located in different memory segments. Therefore the
== operator will return
false . But if we can't override the
== operator , then how do we tell the JVM that we want these two objects to be treated the same?
This is where the .equals() method comes into play . You can override
equals() to check if some objects have the same values for certain fields in order to consider them equal. You can choose which fields to compare. If we say that two
Person objects will be the same only if they have the same age and the same name, then the IDE will generate something like this to automatically create
equals() :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
Let's return to our previous example.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
Yes, we cannot overload the
== operator to compare objects the way we want, but Java gives us another way - the
equals() method , which we can override as we wish.
Keep in mind that if we don't provide our custom version of .equals() (also known as an override) in our class, then the predefined .equals() from the Object class and the == operator will behave the same. The default
equals() method , inherited from
Object , will check if both instances being compared are the same in memory!
Why are we overriding the hashCode() method?
Some data structures in Java, such as
HashSet and
HashMap , store their elements based on a hash function that is applied to those elements. The hash function is
hashCode() . If we have a choice in overriding
the .equals() method , then we should also have a choice in overriding
the hashCode() method . There's a reason for this. After all, the default implementation
of hashCode() , inherited from
Object , considers all objects in memory to be unique! But let's get back to these hash data structures. There is a rule for these data structures.
A HashSet cannot contain duplicate values and a HashMap cannot contain duplicate keys. A HashSet is implemented using
a HashMap in such a way that each
HashSet value is stored as a key in
the HashMap . How does
HashMap work ?
HashMap is a native array with multiple segments. Each segment has a linked list (
linkedList ). This linked list stores our keys.
HashMap finds the correct linkedList for each key using the
hashCode() method , and then iterates through all the elements of that
linkedList and applies the
equals() method to each of those elements to check if that element is contained there.
Duplicate keys are not allowed. When we put something inside
a HashMap , the key is stored in one of these linked lists. Which linked list this key will be stored in is shown by the result of the
hashCode() method for that key. That is, if
key1.hashCode() results in 4, then that
key1 will be stored in the 4th segment of the array in
the LinkedList existing there . By default, the
hashCode() method returns different results for each instance. If we have a default
equals() that behaves like
== , treating all instances in memory as different objects, then there won't be a problem. As you may recall, in our previous example we said that we want
Person instances to be considered equal if their ages and names are the same.
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
Now let's create a map to store these instances as keys with a specific string as the value pair.
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
In the
Person class, we have not overridden the
hashCode method , but we have an overridden
equals method . Since the default
hashCode gives different results for different Java instances
of person1.hashCode() and
person2.hashCode() , there are high chances of getting different results. Our map can end with different
persons in different linked lists.
This goes against the logic
of HashMap .
After all, a HashMap cannot have several identical keys! The point is that the default
hashCode() inherited from the
Object class is not enough. Even after we override the
equals() method of the Person class . That's why we have to override
hashCode() method after we override
equals method . Now let's fix this. We need to override our
hashCode() method so that it takes into account the same fields as
equals() , namely
age and
name .
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
int prime = 31;
return prime*Objects.hash(name, age);
}
In the
hashCode() method we used a simple value (you can use any other values). However, it is suggested to use prime numbers to create fewer problems.
Let's try storing these keys in our HashMap again :
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode() and
person2.hashCode() will be the same. Let's say they are 0.
The HashMap will go to segment 0 and in it
the LinkedList will save
person1 as a key with the value “1”. In the second case, when
HashMap goes to bucket 0 again to store the key
person2 with the value “2”, it will see that another key equal to it already exists there. This way it will overwrite the previous key. And only the key
person2 will exist in our
HashMap . This is how we learned how the
HashMap rule works , which states that you cannot use multiple identical keys!
However, keep in mind that unequal instances can have the same hashcode, and equal instances must return the same hashcode.
GO TO FULL VERSION