GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing zetcode.com — Java equals and hashCode - Complete Tutorial with Examples Java equals and hashCodeLast modified: May 07, 2025
The
These methods are originally defined in the
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
The equals Method
The
Let's define a Person.java
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.java
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 The equals Contract
The
The Person.java
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.java
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 The hashCode Method
The
If you override Person.java
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.java
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 The hashCode Contract
The
The Person.java
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.java
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 Best Practices for equals and hashCodeWhen implementing these methods, consider the following best practices:
Employee.java
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 Common Pitfalls
Several common mistakes can occur when implementing
PitfallsDemo.java
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 Lombok and IDE Generation
Manually writing ToolAssistedExample.java
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, "[email protected]");
LombokPerson lp2 = new LombokPerson("Alice", 30, "[email protected]");
System.out.println("Lombok: lp1 equals lp2: " + lp1.equals(lp2)); // true
IDEGeneratedPerson ip1 = new IDEGeneratedPerson("Carol", 35, "[email protected]");
IDEGeneratedPerson ip2 = new IDEGeneratedPerson("Carol", 35, "[email protected]");
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
RecordExample.java
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", "[email protected]");
UserRecord user2 = new UserRecord(1, "john.doe", "[email protected]");
UserRecord user3 = new UserRecord(2, "jane.doe", "[email protected]");
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 Source
Java Language Specification: Object.equals()
In this article, we've examined Java's AuthorMy name is Jan Bodnar, and I am a dedicated programmer with many years of experience in the field. I began writing programming articles in 2007 and have since authored over 1,400 articles and eight e-books. With more than eight years of teaching experience, I am committed to sharing my knowledge and helping others master programming concepts. List all Java tutorials. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |