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

At a glance

Purpose
General-purpose application development
File extension
.java
Runs in
Java Virtual Machine
Usually used with
JDK and Java libraries

Specifications & further reading

Related Java documentation

Classes
Classes in Java are blueprints for creating objects, defining their properties (fields) and behaviors (methods). Java is a strictly object-oriented language where everything is encapsulated within classes. A class serves as a template that specifies what data an object will store and what operations it can perform.
Interfaces
An interface defines a contract of methods that implementing classes must provide, without specifying how those methods work internally. Unlike a class, an interface cannot be instantiated directly - it only declares what a class can do, not how. A class uses the 'implements' keyword to fulfill an interface's contract, and a single class can implement multiple interfaces, which is Java's way of achieving a form of multiple inheritance.
Abstract Classes
An abstract class is a class that cannot be instantiated directly and may contain both fully implemented methods and abstract methods (declared without a body, which subclasses must implement). Abstract classes sit between interfaces and regular classes: like an interface, they define a contract; like a regular class, they can hold state (fields) and provide shared, already-implemented behavior.
Constructors
A constructor is a special method that runs automatically when an object is created with 'new', typically used to initialize the object's fields. A constructor shares its name with the class and has no return type, not even void. Java supports constructor overloading, letting a class offer multiple ways to construct an object, and constructor chaining with this(...), where one constructor calls another in the same class.