Java equals and hashCode
Last modified: May 07, 2025
The equals
and hashCode
methods play a fundamental
role in Java's object comparison and hashing mechanisms. A well-implemented
equals
method ensures accurate object equality checks, while an
optimized hashCode
method enables efficient use of objects in
hash-based collections such as HashMap
and HashSet
.
Properly overriding these methods is essential for maintaining consistent and
predictable behavior in Java applications.
These methods are originally defined in the Object
class, the root
of Java's class hierarchy. Since all Java objects inherit these methods by
default, understanding their behavior is crucial. By default,
equals
checks for reference equality (whether two object references
point to the same memory address), and hashCode
generates a unique
identifier based on the object's memory location.
However, for custom classes, overriding these methods is often necessary to
provide meaningful equality comparisons based on an object's state (such as
attribute values) rather than reference identity. When overriding
equals
, ensure consistency with hashCode
—objects that
are considered equal according to equals
must return the same hash
code. This alignment prevents unexpected behavior in hash-based collections and
ensures efficient data retrieval.
The equals Method
The equals
method is used to compare two objects for equality.
The default implementation in the Object
class provides reference
equality (using the ==
operator). Most classes should override this
to provide value-based equality, comparing the actual content of objects.
Let's define a Person
class that we will use in our examples.
It includes fields for name, age, and passport number, along with correctly
implemented equals
and hashCode
methods.
package com.zetcode; import java.util.Objects; public class Person { private String name; private int age; private String passportNumber; public Person(String name, int age, String passportNumber) { this.name = name; this.age = age; this.passportNumber = passportNumber; } public String getName() { return name; } public int getAge() { return age; } public String getPassportNumber() { return passportNumber; } @Override public boolean equals(Object o) { // 1. Check if the same object instance if (this == o) return true; // 2. Check if null or different class if (o == null || getClass() != o.getClass()) return false; // 3. Cast to the correct type and compare significant fields Person person = (Person) o; return age == person.age && Objects.equals(name, person.name) && Objects.equals(passportNumber, person.passportNumber); } @Override public int hashCode() { return Objects.hash(name, age, passportNumber); } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", passportNumber='" + passportNumber + "'}"; } }
The following EqualsDemo
class demonstrates the usage of the
equals
method from our Person
class.
package com.zetcode; public class EqualsDemo { public static void main(String[] args) { // Assumes Person.java is compiled and in the classpath, // or in the same project/package. Person p1 = new Person("John Doe", 30, "A123456"); Person p2 = new Person("John Doe", 30, "A123456"); // Same data as p1 Person p3 = new Person("Jane Smith", 25, "B654321"); // Different data System.out.println("p1: " + p1); System.out.println("p2: " + p2); System.out.println("p3: " + p3); System.out.println("p1 equals p2: " + p1.equals(p2)); // true System.out.println("p1 equals p3: " + p1.equals(p3)); // false System.out.println("p1 equals null: " + p1.equals(null)); // false // Comparing with an object of a different type String s = "A string object"; System.out.println("p1 equals String object: " + p1.equals(s)); // false } }
This example demonstrates a proper equals
implementation within the
Person
class. The EqualsDemo
shows how it correctly compares
Person
objects based on their content (value equality). The method
checks for self-comparison, null, and class equality before comparing fields.
The Objects.equals
helper handles null-safe field comparisons.
The equals Contract
The equals
method must adhere to a specific contract defined in
the Java documentation to ensure predictable behavior:
- Reflexive: For any non-null reference value x,
x.equals(x)
must returntrue
. - Symmetric: For any non-null reference values x and y,
x.equals(y)
must returntrue
if and only ify.equals(x)
returnstrue
. - Transitive: For any non-null reference values x, y, and z,
if
x.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
must returntrue
. - Consistent: For any non-null reference values x and y,
multiple invocations of
x.equals(y)
consistently returntrue
or consistently returnfalse
, provided no information used inequals
comparisons on the objects is modified. - Non-null: For any non-null reference value x,
x.equals(null)
must returnfalse
.
The Person.java
class used in the following test is the same as
defined in the previous section.
package com.zetcode; import java.util.Objects; public class Person { private String name; private int age; private String passportNumber; public Person(String name, int age, String passportNumber) { this.name = name; this.age = age; this.passportNumber = passportNumber; } public String getName() { return name; } public int getAge() { return age; } public String getPassportNumber() { return passportNumber; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name) && Objects.equals(passportNumber, person.passportNumber); } @Override public int hashCode() { return Objects.hash(name, age, passportNumber); } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", passportNumber='" + passportNumber + "'}"; } }
The following ContractTest
class demonstrates these contract properties
using instances of our Person
class.
package com.zetcode; public class ContractTest { public static void main(String[] args) { Person a = new Person("Alice", 25, "P123"); Person b = new Person("Alice", 25, "P123"); // Equal to a Person c = new Person("Alice", 25, "P123"); // Equal to a and b // Reflexive: a.equals(a) System.out.println("Reflexive (a.equals(a)): " + a.equals(a)); // true // Symmetric: a.equals(b) should be same as b.equals(a) System.out.println("Symmetric (a.equals(b)): " + a.equals(b)); // true System.out.println("Symmetric (b.equals(a)): " + b.equals(a)); // true // Transitive: if a.equals(b) and b.equals(c), then a.equals(c) boolean abEquals = a.equals(b); boolean bcEquals = b.equals(c); if (abEquals && bcEquals) { System.out.println("Transitive (a.equals(c)): " + a.equals(c)); // true } // Consistent: multiple calls to a.equals(b) return same result System.out.println("Consistent 1 (a.equals(b)): " + a.equals(b)); // true System.out.println("Consistent 2 (a.equals(b)): " + a.equals(b)); // true // Non-null: a.equals(null) should be false System.out.println("Non-null (a.equals(null)): " + a.equals(null)); // false } }
This ContractTest
example helps verify that our Person
class's
equals
method satisfies all parts of the contract. Violating any
of these rules can lead to unpredictable and incorrect behavior, especially
when objects are used in Java Collections Framework classes.
The hashCode Method
The hashCode
method returns an integer hash code value for an
object. This value is primarily used by hash-based collections like
HashMap
, HashSet
, and Hashtable
to efficiently
store and retrieve objects. The general contract for hashCode
is
critical: if two objects are equal according to the equals(Object)
method, then calling the hashCode
method on each of the two
objects must produce the same integer result.
If you override equals
, you must override hashCode
as well.
Our Person
class (shown again below for clarity) already includes a
correct hashCode
implementation.
package com.zetcode; import java.util.Objects; public class Person { private String name; private int age; private String passportNumber; public Person(String name, int age, String passportNumber) { this.name = name; this.age = age; this.passportNumber = passportNumber; } public String getName() { return name; } public int getAge() { return age; } public String getPassportNumber() { return passportNumber; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name) && Objects.equals(passportNumber, person.passportNumber); } @Override public int hashCode() { // Use Objects.hash() to combine hash codes of all fields // used in the equals() method. return Objects.hash(name, age, passportNumber); } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", passportNumber='" + passportNumber + "'}"; } }
The HashCodeDemo
class demonstrates the behavior of this
hashCode
.
package com.zetcode; public class HashCodeDemo { public static void main(String[] args) { Person p1 = new Person("John Doe", 30, "A123456"); Person p2 = new Person("John Doe", 30, "A123456"); // Equal to p1 Person p3 = new Person("Jane Smith", 25, "B654321"); // Not equal to p1 System.out.println("p1: " + p1 + ", hashCode: " + p1.hashCode()); System.out.println("p2: " + p2 + ", hashCode: " + p2.hashCode()); System.out.println("p3: " + p3 + ", hashCode: " + p3.hashCode()); // Crucial check: if p1.equals(p2) is true, then // p1.hashCode() must be equal to p2.hashCode(). boolean equalsContractMet = p1.equals(p2) && (p1.hashCode() == p2.hashCode()); System.out.println("Equal objects (p1, p2) have same hash code: " + equalsContractMet); // true // Note: If p1.equals(p3) is false, their hash codes are not // required to be different, but it's good if they are for performance. System.out.println("p1.equals(p3): " + p1.equals(p3)); // false System.out.println("Are hashCodes of p1 and p3 different? " + (p1.hashCode() != p3.hashCode())); // usually true } }
This example shows a proper hashCode
implementation in the
Person
class using Objects.hash
. This utility method
computes a hash code based on the hash codes of all provided fields. It's
important that all fields used in the equals
method are also used
in the hashCode
calculation. Equal objects (p1
and p2
)
produce the same hash code. Unequal objects (p1
and p3
) should
ideally produce different hash codes to ensure good performance in hash tables.
The hashCode Contract
The hashCode
method must adhere to this contract:
- Consistency: During the same execution of an application,
if an object's fields used in
equals
comparisons do not change, then multiple invocations ofhashCode
must consistently return the same integer. This integer does not need to remain consistent from one execution of an application to another. - Equality correlation: If two objects are equal according
to the
equals(Object)
method, then callinghashCode
on each of the two objects must produce the same integer result. - Inequality implication (desirable, not strict):
If two objects are unequal according to the
equals(Object)
method, it is not required that callinghashCode
on each of the two objects produce distinct integer results. However, programmers should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
The Person.java
class used in the test below is the same one we
have been using, with correct equals
and hashCode
methods.
package com.zetcode; import java.util.Objects; public class Person { private String name; private int age; private String passportNumber; public Person(String name, int age, String passportNumber) { this.name = name; this.age = age; this.passportNumber = passportNumber; } public String getName() { return name; } public int getAge() { return age; } public String getPassportNumber() { return passportNumber; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name) && Objects.equals(passportNumber, person.passportNumber); } @Override public int hashCode() { return Objects.hash(name, age, passportNumber); } @Override public String toString() { return "Person{name='" + name + "', age=" + age + ", passportNumber='" + passportNumber + "'}"; } }
The HashCodeContractTest
verifies these rules.
package com.zetcode; public class HashCodeContractTest { public static void main(String[] args) { Person a = new Person("Alice", 25, "P123"); Person b = new Person("Alice", 25, "P123"); // Equal to a Person c = new Person("Bob", 30, "P456"); // Different from a // Consistency: a.hashCode() called multiple times returns same value int hash1 = a.hashCode(); int hash2 = a.hashCode(); System.out.println("Consistent hash codes (hash1 == hash2): " + (hash1 == hash2)); // true // Equality correlation: a.equals(b) implies a.hashCode() == b.hashCode() boolean equalsIsTrue = a.equals(b); boolean hashCodesEqual = (a.hashCode() == b.hashCode()); System.out.println("Equal objects, equal hash (a.equals(b) && a.hashCode() == b.hashCode()): " + (equalsIsTrue && hashCodesEqual)); // true // Inequality implication: !a.equals(c) // a.hashCode() != c.hashCode() is desirable but not guaranteed. boolean notEqual = !a.equals(c); boolean hashCodesDifferent = (a.hashCode() != c.hashCode()); System.out.println("Unequal objects (a and c), are their hash codes different? " + hashCodesDifferent); // usually true for good hash functions if (notEqual && !hashCodesDifferent) { System.out.println("Note: Unequal objects a and c have a hash collision."); } } }
This HashCodeContractTest
example verifies the hashCode
contract
using our Person
class. While unequal objects can have the
same hash code (a "hash collision"), good hashCode
implementations
aim to minimize collisions to maintain efficient hash table performance.
Best Practices for equals and hashCode
When implementing these methods, consider the following best practices:
- Override both or neither: If you override
equals
, you must overridehashCode
, and vice-versa, to maintain the contract. - Use the same fields: The set of fields used to compute
equals
should be the same fields used to computehashCode
. - Maintain consistency: Ensure your implementations adhere to the contracts for both methods.
- Utilize helper classes: Use
java.util.Objects.equals
for null-safe field comparisons andjava.util.Objects.hash
for conveniently generating hash codes from multiple fields. - Performance: Keep
equals
andhashCode
methods fast and deterministic. Avoid complex computations or I/O operations. - Mutability: Be cautious with mutable objects. If an object's state changes after it's used as a key in a hash map or an element in a hash set, the collection might behave unpredictably. Document clearly if objects are mutable and how that affects equality and hashing. Consider making objects involved in hashing immutable if possible.
- Final methods: Consider making
equals
andhashCode
final
if you want to prevent subclasses from changing their behavior, which can be important if instances of subclasses are mixed with superclass instances in collections.
package com.zetcode; import java.util.Objects; import java.util.HashMap; import java.util.Map; public class Employee { private final int id; // Immutable, unique identifier private String name; private String department; public Employee(int id, String name, String department) { if (name == null || department == null) { throw new NullPointerException("Name and department cannot be null"); } this.id = id; // 'id' is final, set only once this.name = name; this.department = department; } public int getId() { return id; } public String getName() { return name; } public void setName(String name) { if (name == null) throw new NullPointerException("Name cannot be null"); this.name = name; } public String getDepartment() { return department; } public void setDepartment(String department) { if (department == null) throw new NullPointerException("Department cannot be null"); this.department = department; } @Override public final boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Employee)) return false; Employee employee = (Employee) o; return id == employee.id; } @Override public final int hashCode() { return Objects.hash(id); } @Override public String toString() { return "Employee{id=" + id + ", name='" + name + "', department='" + department + "'}"; } public static void main(String[] args) { Employee e1 = new Employee(101, "John Smith", "Engineering"); Employee e2 = new Employee(101, "Johnathan Smith", "HR"); Employee e3 = new Employee(102, "Alice Wonderland", "Engineering"); System.out.println("e1 equals e2 (same ID): " + e1.equals(e2)); // true System.out.println("e1 hashCode == e2 hashCode: " + (e1.hashCode() == e2.hashCode())); // true Map<Employee, String> employeeMap = new HashMap<>(); employeeMap.put(e1, "Access Card A"); e1.setDepartment("Management"); // Change mutable field // Still retrievable as equals/hashCode depend only on 'id' System.out.println("Value for e1 after department change: " + employeeMap.get(e1)); // Access Card A System.out.println("Value for e2 (equal to e1): " + employeeMap.get(e2)); // Access Card A } }
This Employee
example demonstrates a class where equality is based solely
on an immutable unique identifier (id
). The mutable fields (name
and
department
) do not participate in the equals
or hashCode
calculations. This ensures that an Employee
object's hash code remains
constant and its equality is stable, making it safe for hash-based collections
even if mutable attributes change.
Common Pitfalls
Several common mistakes can occur when implementing equals
and
hashCode
, leading to subtle bugs:
- Not overriding
hashCode
whenequals
is overridden: This violates the contract and causes issues with hash-based collections. - Violating the
equals
contract: Especially symmetry or transitivity, often when dealing with inheritance or mixed types. - Using mutable fields in
hashCode
for keys in collections: If a field used inhashCode
changes after an object is added to a hash set or as a key in a hash map, the object may be "lost". - Incorrect
equals
implementation: For instance, usinginstanceof
in a way that breaks symmetry, or not handlingnull
. UsinggetClass() != o.getClass
is often safer for robust equality.
package com.zetcode; import java.util.HashSet; import java.util.Objects; import java.util.Set; public class PitfallsDemo { static class BadSymmetryPoint { private final int x; private final int y; public BadSymmetryPoint(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (this == o) return true; if (o instanceof BadSymmetryPoint) { BadSymmetryPoint that = (BadSymmetryPoint) o; return x == that.x && y == that.y; } if (o instanceof String) { // Problematic comparison return o.equals(String.format("(%d,%d)", x, y)); } return false; } @Override public int hashCode() { return Objects.hash(x,y); } } static class MutableKey { private int value; public MutableKey(int value) { this.value = value; } public void setValue(int value) { this.value = value; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; return value == ((MutableKey) o).value; } @Override public int hashCode() { return Objects.hash(value); } @Override public String toString() { return "MutableKey{value=" + value + "}"; } } public static void main(String[] args) { BadSymmetryPoint p1 = new BadSymmetryPoint(1, 2); String s1 = "(1,2)"; System.out.println("p1.equals(s1): " + p1.equals(s1)); // true System.out.println("s1.equals(p1): " + s1.equals(p1)); // false (Symmetry violated!) System.out.println("---"); Set<MutableKey> keySet = new HashSet<>(); MutableKey key = new MutableKey(42); keySet.add(key); System.out.println("Set contains key (value 42): " + keySet.contains(key)); // true key.setValue(99); // Modify key's state (and hashCode) System.out.println("Set contains modified key (value 99): " + keySet.contains(key)); // false! } }
The PitfallsDemo
class illustrates two common problems. First,
BadSymmetryPoint
violates symmetry in equals
. Second,
MutableKey
shows why using mutable objects as keys in hash collections
is dangerous: modifying the key after insertion can make it "lost".
Lombok and IDE Generation
Manually writing equals
and hashCode
can be error-prone.
Tools like Project Lombok (with @EqualsAndHashCode
) and IDE code
generation features can create these methods automatically, reducing
boilerplate and potential mistakes while ensuring contract adherence.
package com.zetcode; import lombok.EqualsAndHashCode; import java.util.Objects; @EqualsAndHashCode // Lombok: generates equals() and hashCode() class LombokPerson { private String name; private int age; private String email; public LombokPerson(String name, int age, String email) { this.name = name; this.age = age; this.email = email; } } class IDEGeneratedPerson { // Example of IDE-generated methods private String name; private int age; private String email; public IDEGeneratedPerson(String n, int a, String e) { name=n; age=a; email=e; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IDEGeneratedPerson p = (IDEGeneratedPerson) o; return age == p.age && Objects.equals(name, p.name) && Objects.equals(email, p.email); } @Override public int hashCode() { return Objects.hash(name, age, email); } } public class ToolAssistedExample { public static void main(String[] args) { LombokPerson lp1 = new LombokPerson("Alice", 30, "a@e.com"); LombokPerson lp2 = new LombokPerson("Alice", 30, "a@e.com"); System.out.println("Lombok: lp1 equals lp2: " + lp1.equals(lp2)); // true IDEGeneratedPerson ip1 = new IDEGeneratedPerson("Carol", 35, "c@e.com"); IDEGeneratedPerson ip2 = new IDEGeneratedPerson("Carol", 35, "c@e.com"); System.out.println("IDE: ip1 equals ip2: " + ip1.equals(ip2)); // true } }
This example showcases Lombok and typical IDE generation. These tools help maintain correctness, but understanding the underlying principles remains vital.
Simplified Implementation with Java Records
Since Java 14 (standardized in Java 16), records offer a concise
way to create immutable data carriers. The compiler automatically generates
equals
, hashCode
, toString
, a canonical constructor,
and accessor methods for all record components.
package com.zetcode; import java.util.HashSet; import java.util.Set; record UserRecord(int id, String username, String email) { // Compiler auto-generates constructor, accessors, // equals(), hashCode(), and toString() based on all components. } public class RecordExample { public static void main(String[] args) { UserRecord user1 = new UserRecord(1, "john.doe", "john.doe@example.com"); UserRecord user2 = new UserRecord(1, "john.doe", "john.doe@example.com"); UserRecord user3 = new UserRecord(2, "jane.doe", "jane.doe@example.com"); System.out.println("user1 equals user2: " + user1.equals(user2)); // true System.out.println("user1 hashCode == user2 hashCode: " + (user1.hashCode() == user2.hashCode())); // true Set<UserRecord> userSet = new HashSet<>(); userSet.add(user1); System.out.println("Set contains user2 (equal to user1): " + userSet.contains(user2)); // true System.out.println("User1 details: " + user1.toString()); } }
The UserRecord
class, being a Java record, automatically gets correct
equals
and hashCode
. This significantly reduces boilerplate
for data classes and ensures contract adherence.
Source
Java Language Specification: Object.equals()
Java Language Specification: Object.hashCode()
Java Language Features: Records
In this article, we've examined Java's equals
and hashCode
.
We covered contracts, implementation, best practices, pitfalls, and modern
approaches. Proper understanding is essential for correct object comparison
and reliable behavior in Java collections.
Author
List all Java tutorials.