This article helps you understand the two important concepts in the Java language: the equals() and hashCode() methods. You will then be able to apply them into your coding, as well as answering interview questions relate to equals and hashCode in Java programming.

When it comes to working with Java collections, we should override the equals() and hashCode() methods properly in the classes of the elements being added to the collections. Otherwise we will get unexpected behaviors or undesired results.

Read on, as I’m going to help you understand these concepts easily and how to apply them into your daily coding.

You know, the Object class (the super class of all classes in Java) defines two methods equals() and hashCode(). That means all classes in Java (including the ones you created) inherit these methods. Basically, the Object class implements these methods for general purpose so you may not see them frequently.

However, you will have to override them specifically for the classes whose objects are added to collections, especially the hashtable-based collections such as HashSet and HashMap.

Keep reading to understand what are they and why are they so important in Java programming.

 

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:

The equals()method is designed to compare two objects semantically (by comparing the data members of the class), whereas the == operator compares two objects technically (by comparing their references i.e. memory addresses).

NOTE: The implementation of equals()method in the Object class compares references of two objects. That means we should override it in our classes for semantic comparison. Almost classes in the JDK override their own version of equals()method, such as String, Date, Integer, Double, etc.

A typical example is String comparison in Java. Let’s see the following code:

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): true

You 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): false

 

Let’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: false

It’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.

Having said that, when we add objects of a class to a hashtable-based collection (HashSet, HashMap), the class’s hashCode() method is invoked to produce an integer number (which can be an arbitrary value). This number is used by the collection to store and locate the objects quickly and efficiently, as a hashtable-based collection does not maintain order of its elements.

 

NOTE: The default implementation of hashCode() in the Object class returns an integer number which is the memory address of the object. We should override it in our own classes. Almost classes in the JDK override their own version of hashCode()method, such as String, Date, Integer, Double, etc.

  

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.

By following these rules, we keep the collections consistent in maintaining its elements. If we violate these rules, the collections will behave unexpectedly such as the objects cannot be found, or wrong objects are returned instead of the correct ones.

Now, let’s see how the hashCode() and equals() methods affect the behaviors of a Set by coming back to the student example.

Until now, we have the Student class written like this:

/**
 * 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 - 30

Look, 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 - 23

Awesome! 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: true

For 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.

And remember the contract between equals() and hashCode()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.

You can learn more in the Item 9: Always override hashCode when you override equals in the Effective Java book.

I hope you find this article helpful, in terms of understanding why equals and hashCode needed in Java. It's also a good reference for Java job interview questions.

 

Other Java Collections Tutorials:


About the Author:

is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.



Add comment

   


Comments 

#18Vaclav2022-12-13 03:51
Thank you Nam, this is a great explanation, it helped me a lot!
Quote
#17Ardak2021-10-14 12:58
Thank you! It is very simple and professional explanation.
Quote
#16Thor Le2020-11-12 03:53
Nice article,
Thank you so much
Quote
#15Nam2020-09-12 22:49
Hi Tarkeshwar Prasad,
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.
Quote
#14Tarkeshwar Prasad2020-09-12 22:00
Hi Nam,
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.
Quote