Understanding equals() and hashCode() in Java
- Details
- Written by Nam Ha Minh
- Last Updated on 09 March 2024   |   Print Email
1. Understanding the equals() method
When comparing two objects together, Java calls their equals() method which returns true if the two objects are equal, or false otherwise. Note that this comparison using equals()method is very different than using the == operator.Here’s the difference:String s1 = new String("This is a string"); String s2 = new String("This is a string"); boolean refEqual = (s1 == s2); boolean secEqual = (s1.equals(s2)); System.out.println("s1 == s2: " + refEqual); System.out.println("s1.equals(s2): " + secEqual);Can you guess the output? Here is it:
s1 == s2: false s1.equals(s2): trueYou see, the reference comparison (== operator) returns false because s1and s2 are two different objects which are stored in different locations in memory. Whereas the semantic comparison returns true because s1 and s2 has same value (“This is a string”) which can be considered equal semantically.Likewise, let say we have the Student class as following:
/** * Student.java * @author www.codejava.net */ public class Student { private String id; private String name; private String email; private int age; public Student(String id, String name, String email, int age) { this.id = id; this.name = name; this.email = email; this.age = age; } public String toString() { String studentInfo = "Student " + id; studentInfo += ": " + name; studentInfo += " - " + email; studentInfo += " - " + age; return studentInfo; } }In practice, we can consider two Student objects are semantically equal if they have same attributes (id, name, email and age). Now, let’s see how to override the equals() method in this class to confirm that two Student objects having identical attributes are considered to be equal:
/** * Overriding equals() method * (C) www.codejava.net */ public boolean equals(Object obj) { if (obj instanceof Student) { Student another = (Student) obj; if (this.id.equals(another.id) && this.name.equals(another.name) && this.email.equals(another.email) && this.age == another.age) { return true; } } return false; }Here, this equals()method checks if the passed object is of type Student and if it has same attributes as the current object, they are considered to be equal (return true); otherwise they are not equal (return false). Let’s test it out with the following code:
Student student1 = new Student("123", "Tom", "tom@gmail.com", 30); Student student2 = new Student("123", "Tom", "tom@gmail.com", 30); Student student3 = new Student("456", "Peter", "peter@gmail.com", 23); System.out.println("student1 == student2: " + (student1 == student2)); System.out.println("student1.equals(student2): " + (student1.equals(student2))); System.out.println("student2.equals(student3): " + (student2.equals(student3)));And we have the following output:
student1 == student2: false student1.equals(student2): true student2.equals(student3): falseLet’s see another example to understand how overriding the equals()method really helps. Suppose that we have a list of students like this:
List<Student> listStudents = new ArrayList<>();This list contains the three Student objects above:
listStudents.add(student1); listStudents.add(student2); listStudents.add(student3);Now we want to check whether the list contains a student with a given ID. I’ll tell you an easy and interesting solution using equals()method.Note that the List interface provides the contains(Object) method which can be used for checking if the specified object exists in the list. Behind the scene, the list invokes the equals()method on the search object to compare it with other objects in the collection.And do you agree that two Student objects can be considered to be equal if they have same ID? So we update the equals() method in the Student class like this:
/** * Overriding equals() method * (C) www.codejava.net */ public boolean equals(Object obj) { if (obj instanceof Student) { Student another = (Student) obj; if (this.id.equals(another.id)) { return true; } } return false; }Here, this equals() method compares only the ID attribute of two Student objects. And add another constructor to the Student class:
public Student(String id) { this.id = id; }Now, we can perform the checking like this:
Student searchStudent1 = new Student("123"); Student searchStudent4 = new Student("789"); boolean found1 = listStudents.contains(searchStudent1); boolean found4 = listStudents.contains(searchStudent4); System.out.println("Found student1: " + found1); System.out.println("Found student4: " + found4);Here’s the result:
Found student1: true Found student4: falseIt’s awesome, isn’t it? Thanks to the equals() method which makes our code simple. Imagine if we don’t use it, we would have implemented the search functionality more complex like this:
public boolean searchStudent(List<Student> listStudents, String id) { for (Student student : listStudents) { if (student.getId().equals(id)) { return true; } } return false; }
2. Understanding the hashCode() method
The Object class defines the hashCode() method as follows:public int hashCode()
You can see this method returns an integer number. So where is it used?Here’s the secret:This hash number is used by hashtable-based collections like Hashtable, HashSet and HashMap to store objects in small containers called “buckets”. Each bucket is associated with a hash code, and each bucket contains only objects having identical hash code.In other words, a hashtable groups its elements by their hash code values. This arrangement helps the hashtable locates an element quickly and efficiently by searching on small parts of the collection instead the whole collection.Here are the steps to locate an element in a hashtable:- Get hash code value of the specified element. This results in the hashCode() method to be invoked.
- Find the right bucket associated with that hash code.
- Inside the bucket, find the correct element by comparing the specified element with all the elements in the bucket. This results in the equals() method of the specified element to be invoked.
3. The Rules Between equals() and hashCode()
As explained above, as hashtable-based collection locates an element by invoking its hashCode() and equals() methods, so we must obey this contract with regard to the way we override these methods:- When the equals() method is overridden, the hashCode() method must be overridden as well.
- If two objects are equal, their hash codes must be equal as well.
- If two objects are not equal, there’s no constraint on their hash codes (their hash codes can be equal or not).
- If two objects have identical hash codes, there’s no constraint on their equality (they can be equal or not).
- If two objects have different hash codes, they must not be equal.
/** * Student.java * @author www.codejava.net */ public class Student { private String id; private String name; private String email; private int age; public Student(String id) { this.id = id; } public Student(String id, String name, String email, int age) { this.id = id; this.name = name; this.email = email; this.age = age; } public String toString() { String studentInfo = "Student " + id; studentInfo += ": " + name; studentInfo += " - " + email; studentInfo += " - " + age; return studentInfo; } public boolean equals(Object obj) { if (obj instanceof Student) { Student another = (Student) obj; if (this.id.equals(another.id)) { return true; } } return false; } }Note that, there’s only equals() method is overridden till now.We add three Student objects to a HashSet as shown in the following code:
Student student1 = new Student("123", "Tom", "tom@gmail.com", 30); Student student2 = new Student("123", "Tom", "tom@gmail.com", 30); Student student3 = new Student("456", "Peter", "peter@gmail.com", 23); Set<Student> setStudents = new HashSet<Student>(); setStudents.add(student1); setStudents.add(student2); setStudents.add(student3);Now, let’s print information of all students in this set using Lambda expressions:
setStudents.forEach(student -> System.out.println(student));And we have the following output:
Student 456: Peter - peter@gmail.com - 23 Student 123: Tom - tom@gmail.com - 30 Student 123: Tom - tom@gmail.com - 30Look, do you notice that there seems to be 2 duplicate students (ID: 123), right?Oh, we would expect the set does not contain duplicate elements, why is this possible?Here’s the reason:The set invokes the equals() and hashCode()methods on each object being added to ensure there’s no duplication. In our case, the Student class overrides only the equals() method. And the hashCode()method inherited from the Object class returns memory addresses of each object which is not consistent with the equals() method (the contact is violated). Therefore the set treats the student1 and student2 object as two different elements.Now, let’s override the hashCode()method in the Student class to obey the contract of equals() and hashCode(). Here’s the code needs to be added:
public int hashCode() { return 31 + id.hashCode(); }This method returns an integer number based on the hash code of the id attribute (its hashCode()method is overridden by the String class). Run the code to print the set again and observe the result:
Student 123: Tom - tom@gmail.com - 30 Student 456: Peter - peter@gmail.com - 23Awesome! The duplicate element is now removed, you see? That’s exactly what we want.With the equals() and hashCode()methods overridden properly, we can also perform search on the set like this:
Student searchStudent = new Student("456"); boolean found = setStudents.contains(searchStudent); System.out.println("Found student: " + found);Output:
Found student: trueFor more experiments yourself, try to remove either the equals() or hashCode()method and observe the outcome.I hope the above explanation and examples help you understand how the equals() and hashCode()methods work, and why they play important roles with regard to collections.There’s more tips about implementing equals() and hashCode()methods correctly and efficiently, which you can find in the following article:
http://www.javaranch.com/journal/2002/10/equalhash.html
And I recommend you to visit the Javadocs of these couple methods here:4. Summary of equals() and hashCode()
Today, let remember the following points:- The equals() method compares if two objects are equal semantically, e.g. comparing the data members of the class.
- The hashCode()method returns an integer value which is used to distribute elements in buckets of a hashtable-based collection.
- When the equals() method is overridden, the hashCode() method must be overridden as well.
- If two objects are equal, their hash codes must be equal as well.
- If two objects are not equal, there’s no constraint on their hash codes (their hash codes can be equal or not).
- If two objects have identical hash codes, there’s no constraint on their equality (they can be equal or not).
- If two objects have different hash codes, they must not be equal.
Other Java Collections Tutorials:
- Java Set Tutorial
- Java Map Tutorial
- Java List Tutorial and
- Java Queue Tutorial
- Understanding Object Ordering in Java with Comparable and Comparator
- 18 Java Collections and Generics Best Practices
Comments
Thank you so much
You need to get hashCode of the key properties that determine the uniqueness of an object - not all properties. So it's usually the ID field of the class.
In overriding hashCode() method do I need to get hashCode of all the properties available in the class or only one property of hascode is sufficient.