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 returntrueif and only ify.equals(x)returnstrue. - Transitive: For any non-null reference values x, y, and z,
if
x.equals(y)returnstrueandy.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 returntrueor consistently returnfalse, provided no information used inequalscomparisons 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
equalscomparisons do not change, then multiple invocations ofhashCodemust 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 callinghashCodeon 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 callinghashCodeon 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
equalsshould 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.equalsfor null-safe field comparisons andjava.util.Objects.hashfor conveniently generating hash codes from multiple fields. - Performance: Keep
equalsandhashCodemethods 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
equalsandhashCodefinalif 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
hashCodewhenequalsis overridden: This violates the contract and causes issues with hash-based collections. - Violating the
equalscontract: Especially symmetry or transitivity, often when dealing with inheritance or mixed types. - Using mutable fields in
hashCodefor keys in collections: If a field used inhashCodechanges after an object is added to a hash set or as a key in a hash map, the object may be "lost". - Incorrect
equalsimplementation: For instance, usinginstanceofin a way that breaks symmetry, or not handlingnull. UsinggetClass() != o.getClassis 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.