Do you want to know why your HashSet isn't functioning properly? Or why Java treats two things that appear to be identical differently? The correct use of the equals() and hashCode() functions is probably the solution. We will go over the importance of these techniques as well as proper implementation in this extensive tutorial.
Imagine this: In order to store user data in your application, you have developed a custom Person class. Everything appears to be in order until you begin using hashMaps or hashSets, at which point your objects start acting strangely. This is where hashCode() and equals() must be properly implemented.
The equals() method compares two objects for logical equality. By default, the implementation in the Object class examines object references rather than actual content. This action may not be consistent with the intended concept of equality in custom classes.
By default, the hashCode() method returns an integer corresponding to the object's memory address. When working with hash-based collections like HashMap or HashSet, this value specifies the bucket in which the item will be stored.
Java's equals() function simply determines whether two objects point to the same memory location by default. In practical applications, we rarely want this. Think about this instance:
public class Person { String name; public Person(String name) { this.name = name; } public static void main(String[] args) { Person p1 = new Person("John"); Person p2 = new Person("John"); System.out.println(p1.equals(p2)); // Output: false } }
Both equals() and hashCode() are necessary for the correct operation of collections such as HashMap, HashSet, and Hashtable. Without appropriate implementations:
In hash-based collections, the bucket location of objects is determined by their hashCode(). If two logically equal objects have different hash codes, they might end up in different buckets, breaking the functionality of the collection.
Example of inconsistency without overriding:
import java.util.HashSet; public class Person { String name; public Person(String name) { this.name = name; } public static void main(String[] args) { Person p1 = new Person("John"); Person p2 = new Person("John"); HashSet<Person> set = new HashSet<>(); set.add(p1); System.out.println(set.contains(p2)); // Output: false } }
Without proper overrides, p2 is not found in the set despite it being logically equal to p1.
Violating this contract might result in inconsistent and unpredictable behavior, especially when utilizing collections like as HashMap or HashSet.
Example
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return name.equals(person.name); }
@Override public int hashCode() { return 31 * name.hashCode(); }
Modern IDEs, such as IntelliJ IDEA or Eclipse, may auto-generate these methods, ensuring correctness and minimizing boilerplate code.
At DevelopersMonk, we share tutorials, tips, and insights on modern programming frameworks like React, Next.js, Spring Boot, and more. Join us on our journey to simplify coding and empower developers worldwide!