equals() and hashcode() in java

Before starting the about the contract between equals() and hashCode() in Java. We will recommend you should learn about hashCode() and equals() method. You should learn about their default implementation and how to correctly override them.

In this article, we will discuss the most important topic about the equals() and hashCode() in Java. Many programmers want to know why we should override the hashCode() and equals() method. What if we are overriding only one method? When we should override them.

Here is the table content of the article will we will cover this topic.
Where equals() and hashCode() method exists?
What is equal() method?
Why OR When we should Override the equals() method?
What is hashCode() method?
Why OR When we should Override the hashCode() method?

The Contract Between equals() and hashcode()?

Where equals() and hashCode() method exists?

Each method is defined in the java.lang.Object which is the Superclass in Java. Each class inheriting these methods from the Object class. The object class provides these methods for comparing objects. If you are working with a simple class, then these methods will be easy to use or override. But if you are working with several classes in large projects then you must understand the concept of equals() and hashCode() method.

Let’s see the default implementation of hashcode() and equals() method.

// Default implementation of hashCode() method
public native int hashCode();

// Default implementation  of equals() method
public boolean equals(Object obj) {
        return (this == obj);
 }

This is the default implementation of these methods provided in Object class.

What is equal() method

The equals() method is defined in Object class which is the super most class in Java. This method is used to compare two objects and it returns the boolean value based on the comparison.
You can override the equals() method as per your functionality. Suppose you want to compare the objects own equality condition. Then it is recommended to override equals(Object obj) method.

Let’s take the example from the String class.
In the String classequals() method is overridden and provided the equality condition accordingly. For more detail, you should read it from here.

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String aString = (String)anObject;
            if (coder() == aString.coder()) {
                return isLatin1() ? StringLatin1.equals(value, aString.value)
                                  : StringUTF16.equals(value, aString.value);
            }
        }
        return false;
    }

As you can see in the above code the String class overridden the method and comparing two strings. It compares the two given strings based on the content of the string. It returns the boolean value. If both strings are not the same then it returns false otherwise, it returns true.

Why OR When we should Override the equals() method?

The default implementation of the equals() method compares only the references of objects. But sometimes default implementation is not useful, and we want to compare the objects as per our requirements. In these types of situations, it is better to override the equals() method.

Let’s discuss it with example. We will create a user-defined class and let’s see what will be the result of comparison when we will override the equals() method or not?
Let’s take an example of the default implementation of equals() method.

public class Employee 
{
  int empId;
  String name;
  
  public static void main(String[] args) 
  {
    Employee emp1 = new Employee();
    emp1.name = "Ravi";
    emp1.empId = 1;
    
    Employee emp2 = new Employee();
    emp2.name = "Ram";
    emp2.empId = 2;
    
    System.out.println("Is both employee have same id:");
    System.out.println(emp1.equals(emp2));
    
    Employee emp3 = new Employee();
    emp3.name = "Ram";
    emp3.empId = 2;
    
    System.out.println(emp2.equals(emp3));
          }
}

Output: Output: Is both employee have same id:
false
false

Reason: You must saw emp2 and emp3 have the same name and id. But equals() method saying these do not equal because when we compare emp2 and emp3, it is checked whether both emp2 and emp3 refer to the same object or not. The compiler made a call to equals() method of Object class and it compares the references of the object.

The emp2 and emp3 refer to two different objects, hence the value (emp2 == emp3) is false.
If we create another reference say emp4 like following:

Employee emp4 = emp3;
then (emp3 == emp4) will give true.

So, how do we check for equality of values inside the objects?
We can achieve this task by overriding the equals() method of Object class. We can override the equals() method in our class to check whether two objects have the same data or not.

public class Employee 
{
  int empId;
  String name;
  
  public static void main(String[] args) 
  {
    Employee emp1 = new Employee();
    emp1.name = "Ravi";
    emp1.empId = 1;
    
    Employee emp2 = new Employee();
    emp2.name = "Ram";
    emp2.empId = 2;
    
    System.out.println("Is both employee have same id:");
    System.out.println(emp1.equals(emp2));
    
    Employee emp3 = new Employee();
    emp3.name = "Ram";
    emp3.empId = 2;
    
    System.out.println(emp2.equals(emp3));
    
  }
  
  @Override
  public boolean equals(Object obj)
  {
    return ((Employee)obj).empId == this.empId;  
    }
}

Output: Is both employee have same id:false
true

What is hashCode() method

The hashCode() method is defined in Object class which is the super most class in Java. This method returns a hash code value for the object. Let’s have a look at code.

1. It returns an integer value of the object memory address. Some programmer thinks it is the memory address of an object which is not correct. It’s the integer representation of the object’s memory address.
2. It is a native method. By means of the native method, it is written in C/C++ language. As you know, in java we can’t find the memory address of the object so that the hashCode() method is written in C/C++ that helps find the memory address.

As you already know every class in java is the child of Object class. It means each class inheriting the hashCode() method from Object class.
1. Whenever a new Object is created, JVM creates a new entry of Object in memory with the corresponding hashCode.
2. The hashCode() method should return a unique value for every object.
3. If two objects are equal according to equals() method, then their hash code must be the same.

// If obj1 and obj2 are equals and returning true according to the equals() method
Obj1.equals(obj2);
// Then hashCode() of both objects must be equal.
Obj1.hashCode() == obj2.hashCode()

4. If two objects are unequal according to equals() method, their hash code may or may not be equal.

// If obj1 and obj2 are not equals and returning false according to the equals() method
Obj1.equals(obj2);
// Then hashCode() of both objects may be equal or may be not.
Obj1.hashCode() == obj2.hashCode()

Let’s take the example from the String class.
In the String class, the hashCode() method is overridden and provided the equality condition accordingly.

public int hashCode() 
{
        int h = hash;
        if (h == 0 && value.length > 0) {
            hash = h = isLatin1() ? StringLatin1.hashCode(value)
                                  : StringUTF16.hashCode(value);
        }
      return h;
}

As you can see in the above code the String class has overridden the hashCode() method. It returns the int value.

Why OR When we should Override the hashCode() method?

The default implementation of hashCode() method is not provided in Object class. It’s a native method.
Before overriding the hashCode() method. We should understand what’s the need to override it.

Let’s try to understand with the HashSet example. As you already know HashSet stores only unique values. Because it uses the hashCode() method to store the values in the hash table.  Whenever HashSet stores the value, it compares the hash code of each object and adds it to the hash table. In Java, there are some classes that override the hashCode() method. We will see how HashSet stores unique Integer values by use of hashCode() method.

Let’s have a look at the Integer class that overrides the hashCode() method.

@Override
    public int hashCode() 
{
    return Integer.hashCode(value);
}
import java.util.HashSet;
public class HashSetExample 
{
	public static void main(String[] args) 
	{
		HashSet<Integer> setOfInteger = new HashSet<Integer>();
		setOfInteger.add(1);
		setOfInteger.add(2);
		setOfInteger.add(1);
		
		System.out.println("Size of HashSet: "+ setOfInteger.size());
	}
}

Output: Size of HashSet: 2

In the above example, we have added three Integer objects in HashSet, but it contains only two objects. How it removes the duplicity of objects?
STEP 1: When we are adding the first object in HashSet. HashSet stores it inside a bucket and links it to the value of hashcode().

setOfInteger.add(1);

STEP 2: Adding another object in HashSet and adds value as mentioned in the first step.

setOfInteger.add(2);

STEP 3: Now we are adding the same element with the same hash code is inserted into the set. It doesn’t add it. Because HashSet makes a search for an element inside it, and it generates the element’s hash code and looks for a bucket that corresponds to this hash code.HashSet stores its element in Hash Table by use of the unique key. It stores each element in memory buckets. Each bucket has a particular hash code.

setOfInteger.add(1);

The Contract Between equals() and hashcode()?

As per our above discussion, both methods are used for comparison of objects. Both methods should override when we are talking about a huge application that considers two objects as equal when some business fact happens. Even the Object class provides the default implementation, but it is not enough to satisfy business needs.

In this section, we will discuss multiple examples of hashCode() and equals() method. Firstly, we will discuss the problem area.

We will do one example with the default implementation of hashCode() and equals() method.

class Book
{
	int bookId;
	String name;
	public int getBookId() {
		return bookId;
	}
	public void setBookId(int bookId) {
		this.bookId = bookId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Book(int bookId, String name) {
		this.bookId = bookId;
		this.name = name;
	}
}

public class ExampleOfEqualsAndHashCode 
{
	public static void main(String arg[]) 
	{
		Book book1 = new Book(123, "Java");
		Book book2 = new Book(123, "Java");
		
		System.out.println("Is both books are same : "+ book1.equals(book2));
		System.out.println("HashCode of book1 :"+ book1.hashCode());
		System.out.println("HashCode of book2 :"+ book2.hashCode());

	}
}

Output: Is both books are same : false
HashCode of book1 :1556956098
HashCode of book2 :1252585652

In the above example, we are defining a class ExampleOfEqualsAndHashCode that checks whether two instances of Book (who have the exact same attributes) are considered as equal.

It is returning false because when we are calling equals() method then JVM invoking the equals()  method that presented in Object class with the default implementation.

public boolean equals(Object obj) {
        return (this == obj);
    }

As per the observation of default implementation, it is comparing the references of objects. Both instances of Book class(book1 and book2) have the same attribute values but they are stored in different memory locations. So, they are considered different objects.

So how to resolve the problem? We can resolve this problem by overriding of equals() method.

Let’s discuss the same example when we are overriding the equals() method. We are considering that two Books are equal if they have the same bookId, so we override the equals() method and provide our own implementation.

class Book
{
	int bookId;
	String name;
	public int getBookId() {
		return bookId;
	}
	public void setBookId(int bookId) {
		this.bookId = bookId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Book(int bookId, String name) {
		this.bookId = bookId;
		this.name = name;
	}
	
	@Override
	public boolean equals(Object obj) 
	{
	    return this.getBookId() == ((Book) obj).getBookId();
	}
}

public class ExampleOfEqualsAndHashCode 
{
	public static void main(String arg[]) 
	{
		Book book1 = new Book(123, "Java");
		Book book2 = new Book(123, "Java");
		
		System.out.println("Is both books are same : "+ book1.equals(book2));
		System.out.println("HashCode of book1 :"+ book1.hashCode());
		System.out.println("HashCode of book2 :"+ book2.hashCode());

	}
}

Output: Is both books are same : true
HashCode of book1 :1556956098
HashCode of book2 :1252585652

In the above implementation, we are overriding the equals() method and provide own implementation.  We are considering that two Books are equal if and only if they have the same bookId.

It is returning true because when we are calling equals() method then JVM invoking the equals()  method that presented in Book class with implementation.

@Override
	public boolean equals(Object obj) 
	{
	    return this.getBookId() == ((Book) obj).getBookId();
	}

NOTE: We should override the equals() method with best practices. Because this method creates a lot of impact on performance so we must take care. You can read the best practices for the equals() method.

@Override
	public boolean equals(Object obj) 
	{
	    if (obj == null) return false;
	    if (!(obj instanceof Book))
	        return false;
	    if (obj == this)
	        return true;
	    return this.getBookId() == ((Book) obj).getBookId();
	}

We have seen how we should override the equals() method and why we need to override it.

Now we will understand the use of hashCode() method and why we should override it.

The hashCode() method is useful to override when you want to insert the objects into a HashTable, HashMap, and HashSet. If you are working in any application that works with hashTable then you must override the hashCode() method. If you want to know the working on HashSet you can read from here.

Let’s discuss it with example.

import java.util.HashSet;

class Book
{
	int bookId;
	String name;
	public int getBookId() {
		return bookId;
	}
	public void setBookId(int bookId) {
		this.bookId = bookId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Book(int bookId, String name) {
		this.bookId = bookId;
		this.name = name;
	}
	
	@Override
	public boolean equals(Object obj) 
	{
	    return this.getBookId() == ((Book) obj).getBookId();
	}
}

public class ExampleOfEqualsAndHashCode 
{
	public static void main(String arg[]) 
	{
		Book book1 = new Book(123, "Java");
		Book book2 = new Book(123, "Java");
		
		HashSet<Book> setOfBook = new HashSet<Book>();
		setOfBook.add(book1);
		setOfBook.add(book2);
		
		System.out.println("Size of HashSet : "+ setOfBook.size());
	}
}

Output: Size of HashSet : 2

In the above example, both instances have the same attribute but HashSet considered them different.

Reason of the above problem?

Because HashSet uses the hashTable to store the element. When inserting an object into a hashtable. Each value is stored on the base of key and the hash code of the key is calculated. The hash code of key is used to determine where to search for the object.

Step1: So, when we were adding book1 instance in HashSet. Firstly, a unique hashCode was calculated and inserted the object into hashTable. To calculate the hashcode of the key, The JVM invoked the hashCode() method of Object class.

Step2: After adding the book1 instance, we were adding book2 instance. It returned different hashCode when JVM tries to calculate the hashCode of the object. Even both objects are the same.

We can resolve this problem by overriding the hashCode() method.

import java.util.HashSet;

class Book
{
	int bookId;
	String name;
	public int getBookId() {
		return bookId;
	}
	public void setBookId(int bookId) {
		this.bookId = bookId;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Book(int bookId, String name) {
		this.bookId = bookId;
		this.name = name;
	}
	
	@Override
	public boolean equals(Object obj) 
	{
	    return this.getBookId() == ((Book) obj).getBookId();
	}
	
	@Override
	public int hashCode()
	{
		return bookId;
	}
}

public class ExampleOfEqualsAndHashCode 
{
	public static void main(String arg[]) 
	{
		Book book1 = new Book(123, "Java");
		Book book2 = new Book(123, "Java");
		
		HashSet<Book> setOfBook = new HashSet<Book>();
		setOfBook.add(book1);
		setOfBook.add(book2);
		
		System.out.println("Size of HashSet : "+ setOfBook.size());
	}
}

Output: Size of HashSet : 1

Leave a Comment