Equals and HashCode
equals
Method
equals
MethodThe equals
method is used to check for logical equality between two objects, not reference equality. By default, equals
in the Object
class checks if two references point to the same memory address (this == obj
).
Overriding equals
equals
When overriding equals
, it’s essential to ensure that it satisfies certain properties:
Reflexive: For any non-null reference
x
,x.equals(x)
should returntrue
.Symmetric: For any non-null references
x
andy
,x.equals(y)
should returntrue
if and only ify.equals(x)
istrue
.Transitive: For any non-null references
x
,y
, andz
, ifx.equals(y)
istrue
andy.equals(z)
istrue
, thenx.equals(z)
should betrue
.Consistent: Multiple calls to
x.equals(y)
should consistently returntrue
orfalse
, provided that no fields used in the comparison are modified.Non-null: For any non-null reference
x
,x.equals(null)
should returnfalse
.
Implementing equals
equals
A typical equals
implementation in Java involves the following steps:
Reference Check: Return
true
if the references are identical (this == obj
).Null Check: Return
false
if the object is null.Class Check: Return
false
if the classes differ, ensuringequals
works only within the same class.Cast and Field Comparison: Cast the object to the correct type, then compare fields.
Example:
public class Person {
private String name;
private int age;
@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);
}
}
hashCode
Method
hashCode
MethodThe hashCode
method returns an integer hash code that represents the object’s address in memory (default behavior) or a calculated value based on its fields. Hash-based collections like HashMap
and HashSet
rely on hashCode
for quickly locating and storing objects.
hashCode
Contract
hashCode
ContractWhen overriding equals
, we must also override hashCode
to maintain consistency in hash-based collections. The hashCode
contract states:
If two objects are equal according to
equals
, they must return the samehashCode
.If two objects are not equal, their
hashCode
can be the same or different.
Implementing hashCode
hashCode
A good
hashCode
function should evenly distribute hash codes across the hash table to minimize collisions.Use prime numbers in hash code calculations, such as
31
, to reduce hash collisions and improve distribution.
Here’s a basic implementation using name
and age
:
@Override
public int hashCode() {
return Objects.hash(name, age); // Generates a hash code for a sequence of input values. The hash code is generated as if all the input values were placed into an array, and that array were hashed by calling Arrays. hashCode(Object[]
}
Or manually:
@Override
public int hashCode() {
int result = 17; // Non-zero constant
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + age;
return result;
}
Custom Hashing Strategies
For advanced scenarios where default hash code algorithms don’t perform well, we can use custom hashing strategies or external libraries like Google Guava’s Hashing
class, especially for complex objects.
Effective Use in Large Hash-Based Collections
To optimize performance in large collections:
Ensure a balanced distribution by selecting fields that offer a unique representation of the object.
Benchmark and analyze hash collisions if the collection is very large or performance-sensitive.
Using equals
and hashCode
in Collections
equals
and hashCode
in CollectionsHash-based Collections: Collections like
HashMap
,HashSet
, andHashtable
usehashCode
for efficient storage and retrieval.Example: When we add an object to a
HashSet
, itshashCode
determines the bucket it’s placed in.equals
checks if an object with the same key already exists.Collisions: If two objects have the same
hashCode
but aren’t equal, they’re stored in the same bucket (collision), andequals
checks help identify distinct objects within the bucket.
Testing equals
and hashCode
equals
and hashCode
Testing equals
and hashCode
helps ensure correctness. Here’s how to test them:
Equality Tests: Check that
equals
adheres to reflexive, symmetric, and transitive properties.Hash Code Consistency: Verify that equal objects have the same hash code.
Non-equality: Ensure that different objects or objects with different values in significant fields return
false
forequals
and have different hash codes.
import java.util.Objects;
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 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);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Pom dependency for test framework
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Test;
import test.Person;
class PersonTest {
@Test
void testEqualsAndHashCode() {
Person p1 = new Person("John", 25);
Person p2 = new Person("John", 25);
Person p3 = new Person("Doe", 30);
assertEquals(p1, p2);
assertEquals(p1.hashCode(), p2.hashCode());
assertNotEquals(p1, p3);
assertNotEquals(p1.hashCode(), p3.hashCode());
}
}
equals
and hashCode
in inheritance
equals
and hashCode
in inheritanceWhen dealing with equals()
and hashCode()
in inheritance, we must ensure:
Consistency – If two objects are equal, their hash codes must be the same.
Symmetry – If
child.equals(parent)
, thenparent.equals(child)
should also be true.Liskov Substitution Principle (LSP) – A subclass object should behave correctly when treated as a superclass object.
Example 1: equals()
and hashCode()
with inheritance (Correct Approach)
import java.util.Objects;
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;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
class Employee extends Person {
private String department;
public Employee(String name, int age, String department) {
super(name, age);
this.department = department;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
if (obj instanceof Employee employee) {
return Objects.equals(department, employee.department);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), department);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("John", 30);
Person p2 = new Person("John", 30);
Employee e1 = new Employee("John", 30, "IT");
Employee e2 = new Employee("John", 30, "IT");
System.out.println(p1.equals(p2)); // true
System.out.println(e1.equals(e2)); // true
System.out.println(p1.equals(e1)); // false (Different classes)
System.out.println(e1.hashCode() == e2.hashCode()); // true (Consistent hashCode)
}
}
Person
overridesequals()
andhashCode()
correctly.Employee
first callssuper.equals()
, ensuring it correctly compares inherited fields.hashCode()
inEmployee
includessuper.hashCode()
, ensuring consistency.
Example 2: Using instanceof
for Flexible Inheritance
If we want a flexible equals()
that allows comparing a subclass with a superclass, we can modify it like this:
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 instanceof Person person)) return false;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
class Employee extends Person {
private String department;
public Employee(String name, int age, String department) {
super(name, age);
this.department = department;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
if (obj instanceof Employee employee) {
return Objects.equals(department, employee.department);
}
return true; // Allow `Person.equals(Employee)`
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), department);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("John", 30);
Employee e1 = new Employee("John", 30, "IT");
System.out.println(p1.equals(e1)); // true (Allowed by instanceof)
System.out.println(e1.equals(p1)); // true (Still symmetric)
}
}
Using
instanceof
instead ofgetClass()
makesequals()
flexible.Person
objects can be considered equal toEmployee
objects if their fields match.
Best Practices
Always override
hashCode
when we overrideequals
.Use immutable fields in
equals
andhashCode
whenever possible.Avoid using transient or derived fields (fields that change frequently or aren’t part of the core identity of the object).
Use IDE auto-generation or Lombok annotations to simplify correct implementation.
Test
equals
andhashCode
to confirm they fulfill their contracts.
Additional Info
Object Equality CheckLast updated
Was this helpful?