Codectionary / Developer documentation / Java

equals(), hashCode(), and toString()

Every Java class inherits equals(), hashCode(), and toString() from Object, but their default implementations are rarely useful - default equals() checks reference identity (like ==), default hashCode() is based on memory address, and default toString() prints an unreadable class name plus hash code. Overriding all three properly is essential for any class you plan to compare by value, print for debugging, or store in hash-based collections like HashMap or HashSet.

Syntax

@Override\npublic boolean equals(Object o) { }\n@Override\npublic int hashCode() { }\n@Override\npublic String toString() { }

Examples

The Problem with Default Behavior

Without overrides, two objects with identical data are not considered equal.

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);

System.out.println(p1 == p2);        // false - different objects
System.out.println(p1.equals(p2));   // false - default equals() is the same as ==
System.out.println(p1);               // Point@1b6d3586 - unreadable default toString()

Overriding toString()

Providing a readable, meaningful string representation of an object.

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }
}

Point p = new Point(3, 4);
System.out.println(p);  // Point(3, 4) - much more useful for debugging

Overriding equals() and hashCode() Together

The contract requires overriding both together - equal objects must produce the same hash code.

import java.util.Objects;

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return x == other.x && y == other.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);
System.out.println(p1.equals(p2));  // true - compares field values now

java.util.Set<Point> points = new java.util.HashSet<>();
points.add(p1);
System.out.println(points.contains(p2));  // true - relies on both equals() AND hashCode()

Best practices

  • Always override equals() and hashCode() together - overriding just one breaks the contract that equal objects must have equal hash codes
  • Use java.util.Objects.hash(field1, field2, ...) for a quick, reliable hashCode() implementation instead of writing the hashing logic by hand
  • Override toString() on any class you plan to log or print for debugging - the default output is not useful
  • Consider a record instead of a manual class when all you need is a simple immutable value type - it generates all three methods correctly for you

Runtime & versions

Java Virtual Machine. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

Java Language Specification

Specifications & further reading