View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All

Equals and Hashcode in Java: The Complete Guide

Updated on 19/05/2025470 Views

Proper implementation of the equals() and hashCode() methods is critical for creating Java applications, especially when working with collections.The equals and hashcode in Java are fundamental methods from the Object class that every Java developer must understand.In this guide, we'll explore the equals and hashcode contract in Java Programming, examine proper implementation techniques, and discuss common pitfalls to avoid when overriding these methods.

Looking to go pro in Java? upGrad’s software engineering course offers hands-on training and expert guidance. 

Understanding equals() and hashCode() Methods in Java

Before diving into the contract between equals and hashcode, let's understand what these methods do individually.

The equals() Method in Java

The equals() method in Java is used to compare the equality of two objects. By default, the equals() method provided by the Object class compares object references, essentially checking if two references point to the same memory location.

Here's the default implementation in the Object class:

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

This default implementation is often insufficient for custom classes where logical equality matters more than reference equality. For example, two Person objects with the same name and age might be considered equal even if they are different objects in memory.

The hashCode() Method in Java

The hashCode() method returns an integer value that represents the object in hash-based collections like HashMap, HashSet, and Hashtable. The default implementation in the Object class typically returns a memory address-based hash value.

Here's how you might think of the default hashCode() method:

public int hashCode() {
    // Implementation depends on JVM, but typically involves memory address
    return /* some computation based on object's memory address */;
}

Become a versatile developer with these professional programs:Full Stack Development Bootcamp – upGradAI Full Stack Developer Course – IIIT BangaloreCertificate in Cloud & DevOps – upGrad

The Equals and Hashcode Contract in Java

The equals and hashcode contract in Java consists of specific rules that must be followed when overriding these methods. This contract ensures hash-based collections work correctly with your custom classes.

The Contract Rules

  1. Consistency with equals(): If two objects are equal according to the equals() method, they MUST have the same hash code.
  2. Hash code consistency: During the execution of an application, an object's hash code should remain consistent unless the object's state used in equals() comparisons changes.
  3. Not required for unequal objects: Two unequal objects MAY have the same hash code (known as a collision).

What Happens If You Break the Contract?

Breaking the equals and hashcode contract in Java can lead to unpredictable behavior, especially with hash-based collections:

  1. Objects might not be found: If an object's hash code changes after being stored in a HashMap, the object might not be retrievable.
  2. Duplicate entries: If two equal objects have different hash codes, they might both be stored in a HashSet, violating its uniqueness guarantee.
  3. Inconsistent behavior: Applications relying on the contract might behave inconsistently if the contract is broken.

Let's see a concrete example of what happens when we break the contract:

// Breaking the equals-hashCode contract
public class ContractBreaker {
    private String value;
    
    public ContractBreaker(String value) {
        this.value = value;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        ContractBreaker other = (ContractBreaker) obj;
        return value.equals(other.value);
    }
    
    // We're NOT overriding hashCode() - breaking the contract!
    
    public static void main(String[] args) {
        Set<ContractBreaker> set = new HashSet<>();
        
        ContractBreaker cb1 = new ContractBreaker("test");
        ContractBreaker cb2 = new ContractBreaker("test");
        
        // These objects are equal according to equals()
        System.out.println("cb1.equals(cb2): " + cb1.equals(cb2));
        
        // But they have different hash codes
        System.out.println("cb1.hashCode(): " + cb1.hashCode());
        System.out.println("cb2.hashCode(): " + cb2.hashCode());
        
        // Adding both to a HashSet
        set.add(cb1);
        set.add(cb2);
        
        // HashSet should contain only unique elements
        // But since we broke the contract, both objects are added
        System.out.println("Set size: " + set.size());
    }
}

Expected Output:

cb1.equals(cb2): true

cb1.hashCode(): 123456789  // Actual values will differ

cb2.hashCode(): 987654321  // Actual values will differ

Set size: 2

Explanation: In this example, we broke the contract by overriding equals() but not hashCode(). Even though cb1 and cb2 are equal according to our equals() implementation, they have different hash codes. As a result, HashSet treats them as different objects and stores both, violating the uniqueness guarantee of the set.

Proper Implementation of equals() and hashCode()

Let's look at the proper way to implement equals and hashcode in Java.

Implementing equals() Method

A correct implementation of equals() should follow these principles:

  1. Reflexivity: An object must be equal to itself (x.equals(x) should return true).
  2. Symmetry: If x.equals(y) returns true, then y.equals(x) should also return true.
  3. Transitivity: If x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  4. Consistency: Multiple invocations of x.equals(y) should consistently return the same result.
  5. Non-nullity: x.equals(null) should return false.

Here's a proper implementation:

@Override
public boolean equals(Object obj) {
    // Check if the compared object is the same instance
    if (this == obj) return true;
    
    // Check if the compared object is null or different class
    if (obj == null || getClass() != obj.getClass()) return false;
    
    // Cast to the specific class
    Person person = (Person) obj;
    
    // Compare the relevant fields
    return age == person.age && 
           (name == null ? person.name == null : name.equals(person.name));
}

Implementing hashCode() Method

A proper hashCode() implementation should:

  1. Be consistent with equals(): Fields used in equals() should be used in hashCode().
  2. Distribute hash codes uniformly across integers.
  3. Be efficient to compute.

Here's a good implementation:

@Override
public int hashCode() {
    // Use the same fields as in equals()
    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + (name == null ? 0 : name.hashCode());
    return result;
}

Putting It All Together: A Complete Example

public class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        Person person = (Person) obj;
        
        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }
    
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
    
    public static void main(String[] args) {
        Person person1 = new Person("John", 30);
        Person person2 = new Person("John", 30);
        Person person3 = new Person("Jane", 25);
        
        // Testing equals()
        System.out.println("person1.equals(person2): " + person1.equals(person2));
        System.out.println("person1.equals(person3): " + person1.equals(person3));
        
        // Testing hashCode()
        System.out.println("person1.hashCode(): " + person1.hashCode());
        System.out.println("person2.hashCode(): " + person2.hashCode());
        System.out.println("person3.hashCode(): " + person3.hashCode());
        
        // Testing with HashSet
        Set<Person> persons = new HashSet<>();
        persons.add(person1);
        persons.add(person2);
        persons.add(person3);
        
        System.out.println("HashSet size: " + persons.size());
    }
}

Expected Output:

person1.equals(person2): true

person1.equals(person3): false

person1.hashCode(): 2142223

person2.hashCode(): 2142223

person3.hashCode(): 2038272

HashSet size: 2

Explanation: In this example, person1 and person2 are considered equal because they have the same name and age. As required by the hashcode and equals contract in Java, they also have the same hash code. When added to a HashSet, only one instance is stored due to the equality. person3 has different field values, resulting in a different hash code, and is stored as a separate entry in the HashSet.

The Hashcode Method in Java: Best Practices

Let's explore some best practices for implementing the hashcode method in Java:

1. Use the Same Fields as in equals()

Any field used in the equals() method should also be used in the hashCode() method. This ensures consistency between the two methods and maintains the contract.

2. Use Prime Numbers

Using prime numbers in hash code calculations helps distribute the hash codes more uniformly:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + field1;
    result = prime * result + (field2 == null ? 0 : field2.hashCode());
    // Add more fields as needed
    return result;
}

3. Handle Arrays Correctly

If your class contains arrays, use Arrays.hashCode() or Arrays.deepHashCode() for nested arrays:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + Arrays.hashCode(arrayField);
    return result;
}

4. Use Objects.hash() for Convenience

Java 7 introduced Objects.hash() as a convenient way to compute hash codes:

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

5. Consider Caching Hash Codes for Immutable Objects

For immutable classes, consider caching the hash code to improve performance:

public final class ImmutablePerson {
    private final String name;
    private final int age;
    private final int hashCode;
    
    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
        this.hashCode = computeHashCode();
    }
    
    private int computeHashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + age;
        result = prime * result + (name == null ? 0 : name.hashCode());
        return result;
    }
    
    @Override
    public int hashCode() {
        return hashCode;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        ImmutablePerson person = (ImmutablePerson) obj;
        
        if (age != person.age) return false;
        return name != null ? name.equals(person.name) : person.name == null;
    }
}

Equals and Hashcode Contract in Java: Advanced Topics

Inheritance and the equals-hashCode Contract

Inheritance complicates the implementation of equals() and hashCode(). Here are some considerations:

  1. Liskov Substitution Principle: Subclasses should be substitutable for their superclasses, which can be tricky with equals().
  2. Composition over Inheritance: Prefer composition when equality becomes complex in inheritance hierarchies.

Let's see an example of the challenges with inheritance:

class Shape {
    private String color;
    
    public Shape(String color) {
        this.color = color;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        Shape shape = (Shape) obj;
        return color.equals(shape.color);
    }
    
    @Override
    public int hashCode() {
        return color.hashCode();
    }
}

class Circle extends Shape {
    private double radius;
    
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!super.equals(obj)) return false;
        // obj must be a Circle at this point, so safe to cast
        Circle circle = (Circle) obj;
        return Double.compare(circle.radius, radius) == 0;
    }
    
    @Override
    public int hashCode() {
        return 31 * super.hashCode() + Double.hashCode(radius);
    }
}

Using equals() and hashCode() with Collections

Hash-based collections rely heavily on proper equals() and hashCode() implementations:

public class StudentRepository {
    private Map<Student, List<Course>> enrollments = new HashMap<>();
    
    public void enroll(Student student, Course course) {
        enrollments.computeIfAbsent(student, k -> new ArrayList<>()).add(course);
    }
    
    public List<Course> getCoursesForStudent(Student student) {
        return enrollments.getOrDefault(student, Collections.emptyList());
    }
    
    public static void main(String[] args) {
        StudentRepository repository = new StudentRepository();
        
        // Create a student and enroll in courses
        Student student1 = new Student("S123", "Alice");
        repository.enroll(student1, new Course("CS101"));
        
        // Create an "equal" student object with the same ID
        Student student2 = new Student("S123", "Alice");
        
        // Retrieve courses for student2 - this works correctly only if
        // equals() and hashCode() are properly implemented
        List<Course> courses = repository.getCoursesForStudent(student2);
        System.out.println("Courses: " + courses.size());
    }
}

class Student {
    private String id;
    private String name;
    
    // Constructor, getters, and setters...
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return id.equals(student.id);
    }
    
    @Override
    public int hashCode() {
        return id.hashCode();
    }
}

class Course {
    private String code;
    
    // Constructor, getters, and setters...
    
    // Proper equals() and hashCode() implementations...
}

Expected Output:

Courses: 1

Explanation: Because equals() and hashCode() are properly implemented based on the student ID, student2 is considered equal to student1. This allows student2 to be used as a key to retrieve the courses that were enrolled using student1.

Equals and Hashcode in Java: Common Pitfalls

1. Forgetting to Override hashCode() When Overriding equals()

This is the most common violation of the hashcode and equals method in Java contract:

// INCORRECT: Overriding equals() without hashCode()
@Override
public boolean equals(Object obj) {
    // Implementation...
}
// No hashCode() override!

2. Using Mutable Fields for Hash Codes

Using mutable fields in hashCode() can cause objects to "disappear" in collections:

// PROBLEMATIC: Using mutable field for hash code
public class MutablePerson {
    private String name;  // Mutable
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public int hashCode() {
        return name.hashCode();  // Based on mutable field
    }
    
    // equals() implementation...
}

3. Inconsistent equals() Implementation

Violating the symmetry or transitivity properties:

// INCORRECT: Asymmetric equals()
public class AsymmetricEquals {
    private String value;
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof String) {
            return value.equals(obj);  // Compares with String
        }
        if (obj instanceof AsymmetricEquals) {
            return value.equals(((AsymmetricEquals) obj).value);
        }
        return false;
    }
    
    // hashCode() implementation...
}

4. Inefficient hashCode() Calculations

Creating unnecessary objects or performing expensive operations:

// INEFFICIENT: Creating temporary objects
@Override
public int hashCode() {
    return new StringBuilder(firstName).append(lastName).toString().hashCode();
}

5. Not Using All Fields from equals() in hashCode()

If a field is important enough to check in equals(), it should be included in hashCode():

// INCORRECT: Missing fields in hashCode()
public class IncompleteHash {
    private String name;
    private int age;
    
    @Override
    public boolean equals(Object obj) {
        // Checks both name and age
        // ...
    }
    
    @Override
    public int hashCode() {
        return name.hashCode();  // Only uses name, ignores age
    }
}

Using IDE and Tools for equals() and hashCode()

Modern IDEs can generate these methods for you:

IntelliJ IDEA

  1. Right-click in the class
  2. Select "Generate" or press Alt+Insert
  3. Choose "equals() and hashCode()"
  4. Select the fields to include

Eclipse

  1. Right-click in the class
  2. Select "Source" > "Generate hashCode() and equals()"
  3. Select the fields to include

Lombok Library

With Lombok, you can use annotations:

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Person {
    private String name;
    private int age;
    // No need to manually implement equals() and hashCode()
}

Conclusion

Properly implementing equals and hashcode in Java is essential for creating robust applications, particularly when working with collections. 

By understanding and following the equals and hashcode contract, you can ensure your objects behave predictably in all contexts.

 Remember that both methods must be consistent with each other and based on the same fields. When in doubt, use modern tools like IDE generators or libraries like Lombok to help create correct implementations.

The key takeaways from this guide:

  • Always override both equals() and hashCode() methods together
  • Use the same fields in both methods
  • Follow the five principles of equals(): reflexivity, symmetry, transitivity, consistency, and non-nullity
  • Create efficient, well-distributed hash codes using prime numbers
  • Be cautious with mutable fields and inheritance hierarchies
  • Use tools and libraries when available to reduce the risk of errors

By following these guidelines, you'll avoid common pitfalls and ensure your Java applications work correctly with collections and comparison operations.

FAQ

1. What is the purpose of equals() method in Java?

The equals() method is used to compare two objects for equality. It determines whether one object is considered equal to another, based on the specific criteria defined in the method's implementation. While the default implementation compares object references, custom implementations typically compare object content.

2. What is the relationship between equals() and hashCode() in Java?

The relationship is defined by the contract: if two objects are equal according to the equals() method, they must have the same hash code. This contract ensures that hash-based collections like HashMap and HashSet work correctly. However, two objects with the same hash code are not necessarily equal.

3. Why should I override both equals() and hashCode() methods?

You should override both methods to maintain the contract between them. If you override only equals(), two equal objects might have different hash codes (breaking the contract). If you override only hashCode(), objects with the same hash code might not be equal. Both scenarios can cause unexpected behavior with hash-based collections.

4. How does hashCode() affect HashMap performance?

The hashCode() method determines which bucket in a HashMap an object goes into. A good hash function distributes objects evenly across buckets, resulting in O(1) average access time. Poor hash functions can lead to many objects in the same bucket, degrading performance to O(n) in the worst case.

5. Can two objects have the same hash code but be different?

Yes, this is called a hash collision. Since the hash code is an integer and there are more possible objects than integer values, collisions are inevitable. That's why hash-based collections also use the equals() method to distinguish between objects with the same hash code.

6. What happens if I change an object's fields after adding it to a HashSet?

If you change fields used in the hashCode() calculation after adding an object to a hash-based collection, the object might "disappear" in the collection. This is because the collection will look for it in a different bucket based on its new hash code.

7. Is it a good idea to use random numbers in hashCode()?

No, random numbers violate the contract requiring consistency. A hash code should be deterministic and based on the object's state that's relevant to equality. Random numbers would cause the same object to have different hash codes across different invocations.

8. Can I just return a constant value from hashCode()?

While technically this doesn't violate the contract, it defeats the purpose of having hash codes. All objects would be placed in the same bucket in hash-based collections, reducing their performance to that of a linked list (O(n) instead of O(1)).

9. How do I handle null fields in equals() and hashCode()?

In equals(), use the null-safe comparison: (field == null ? other.field == null : field.equals(other.field)). In hashCode(), use a default value for null fields: (field == null ? 0 : field.hashCode()). Alternatively, use Objects.equals() and Objects.hash() methods which handle null values properly.

10. What's the best way to compare floating-point fields in equals()?

Use Double.compare() or Float.compare() methods instead of the == operator to account for special values like NaN and to handle precision issues.

11. Can equals() return true when comparing objects of different classes?

While technically possible, it's generally not recommended as it can violate the symmetry requirement of the contract. If a.equals(b) returns true, then b.equals(a) should also return true, which is difficult to ensure across different classes.

12. What's the difference between == and equals() in Java?

The == operator compares object references (checks if two references point to the same object in memory). The equals() method, when properly overridden, compares the logical equality of objects based on their content or business identity.

image

Take the Free Quiz on Java

Answer quick questions and assess your Java knowledge

right-top-arrow
image
Join 10M+ Learners & Transform Your Career
Learn on a personalised AI-powered platform that offers best-in-class content, live sessions & mentorship from leading industry experts.
advertise-arrow

Free Courses

Explore Our Free Software Tutorials

upGrad Learner Support

Talk to our experts. We are available 7 days a week, 9 AM to 12 AM (midnight)

text

Indian Nationals

1800 210 2020

text

Foreign Nationals

+918068792934

Disclaimer

1.The above statistics depend on various factors and individual results may vary. Past performance is no guarantee of future results.

2.The student assumes full responsibility for all expenses associated with visas, travel, & related costs. upGrad does not provide any a.