equals & hashCode, why they belong together.

When exploring the implementation of a concrete class, such as ArrayList, you might have noticed that it overrides the equals() and hashCode() methods. This is not something which is specific to a feature heavy class like ArrayList, but is something which can be observed in many concrete implementations. Ever wondered why? Why would Java not be able to compare two Objects if we just override equals()?

Well, Java can absolutely compare objects correctly with only an overridden equals(). Let’s take a look at the below example. We have a simple Student class, which has two fields name and roll number.

public class Student {  
  
  String name;  
  int rollNumber;  
  
  public Student(String name, int rollNumber) {  
    this.name = name;  
    this.rollNumber = rollNumber;  
  }  
  
  @Override  
  public String toString() {  
    return "Student(name is " + this.name + ". Roll Number is " + this.rollNumber + ")";  
  }  
  
  @Override  
  public boolean equals(final Object object) {  
    if (object == this) {  
      return true;  
    }  
    if (!(object instanceof Student student)) {  
      return false;  
    }  
    return Objects.equals(this.name, student.name) && this.rollNumber == student.rollNumber;  
  }  
}

Now, if we compare two students, they should not be equal, right?

void main() {  
  Student tom = new Student("Tom", 1);  
  Student dan = new Student("Dan",  2);  
  Student tom2 = new Student("Tom",  3);  
  
  System.out.println(tom.equals(dan));  
  System.out.println(tom.equals(tom2));
  System.out.println("Tom's hashCode "+ tom.hashCode());  
  System.out.println("Second Tom's hashCode "+ tom2.hashCode());
}

Output:
false
false
Tom's hashCode 821270929
Second Tom's hashCode 1247233941

Neither the two Tom objects nor their hash codes are equal, so why do we need to override both equals and hashcode? To be sure they have different hash codes we even got their individual hashcode and verified that they are different. So why override hashcode() method? It’s probably just a waste of time and unwanted boilerplate code. You create the students without overriding hashcode().

Student tom = new Student("Tom", 1);
Student dan = new Student("Dan",  2);
Student tom2 = new Student("Tom",  3);
Student ava = new Student("Ava",  4);
Student katie = new Student("Katie",  5);
Student dan2 = new Student("Dan",  2);

After a while, you get a business requirement where you have to add all the students into a HashSet for some business logic. You add all of them and go about your merry way.

Set<Student> uniqueStudents = new HashSet<>();  
  
uniqueStudents.add(tom);  
uniqueStudents.add(dan);  
uniqueStudents.add(tom2);  
uniqueStudents.add(ava);  
uniqueStudents.add(katie);  
uniqueStudents.add(dan2);

A day later, you get back a bug report saying that the set contains duplicate students. How can there be duplicate students? And that too in a Set!!! You go back and do a quick check uniqueStudents.size() and you get 6. But then you notice the subtle copy / paste bug. Student dan2 = new Student("Dan", 2) Second Dan’s roll number was not updated.

When a.equals(b) returns true then a.hashCode() and b.hashCode() must return the same value. However, equal hash codes do not necessarily mean that two objects are equal.

In other words

Student stud1 = new Student("Student1", 1);  
Student stud2 = new Student("Student1", 1);  
Student stud3 = new Student("Student1", 1);  
  
System.out.println("HashCodes "+ stud1.hashCode() +" "+ stud2.hashCode()+" "+stud3.hashCode());
System.out.println(stud1.equals(stud2));

Output:
HashCodes 821270929 1160460865 1247233941
true

So, how do we override the hashCode() method?

@Override
public int hashCode() {
  return Objects.hash(this.name, this.rollNumber);
}

With the hashcode() implementation, the above example will now produce consistent hash codes and adding Dan twice to a HashSet will only result adding a single object into the HashSet, even though they are different objects.

Set<Student> uniqueStudents = new HashSet<>();

uniqueStudents.add(new Student("Dan", 2));
uniqueStudents.add(new Student("Dan", 2));

System.out.println(uniqueStudents.size());

Output:
1

So, the next time you override equals(), don’t forget its partner, hashCode(). They might seem like two unrelated methods, but they have an important contract to uphold. Two objects can be different in memory yet logically equal. And if Java considers them equal, their hash codes must agree too.

This article was written entirely by me. An LLM was used solely for editorial review. All code examples and explanations were written and verified by me.