OOP

27. Key Object Methods to Override

Object provides toString(), equals(), hashCode(), clone(), and finalize() โ€” only toString(), equals(), and hashCode() should routinely be overridden; prefer records for data classes.

Java 21 (core language)

Explanation

toString(): called by System.out.println, string concatenation, and logging. Default is ClassName@hexHashCode โ€” almost never useful. Always override in domain classes or use records.

equals() and hashCode(): covered together in entry #9. Must be overridden together; records do this automatically.

clone(): the Cloneable interface is a poorly-designed marker; Object.clone() does a shallow copy and requires catching CloneNotSupportedException. The preferred alternative is a copy constructor (new Foo(other)) or a static factory. Avoid implementing clone() in new code.

finalize(): called by the GC before an object is reclaimed. Deprecated since Java 9 and removed from consideration in modern Java. It is non-deterministic, slow, and error-prone. Use try-with-resources or Cleaner (Java 9+) instead.

Example

import java.util.Objects;

public class ObjectMethods {
    static class Point {
        final int x, y;

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

        // Copy constructor โ€” preferred over clone()
        Point(Point other) { this(other.x, other.y); }

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

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

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

    // Records do everything automatically (Java 16+)
    record RecordPoint(int x, int y) {}

    public static void main(String[] args) {
        Point p1 = new Point(1, 2);
        Point p2 = new Point(1, 2);
        Point p3 = new Point(3, 4);

        System.out.println(p1);              // Point(1, 2)
        System.out.println(p1.equals(p2));   // true
        System.out.println(p1.equals(p3));   // false
        System.out.println(p1.hashCode() == p2.hashCode()); // true

        // Copy constructor
        Point copy = new Point(p1);
        copy = new Point(copy.x + 1, copy.y); // p1 unchanged
        System.out.println(p1 + " vs " + copy);

        // Record: toString, equals, hashCode all correct
        RecordPoint r1 = new RecordPoint(1, 2);
        RecordPoint r2 = new RecordPoint(1, 2);
        System.out.println(r1);            // RecordPoint[x=1, y=2]
        System.out.println(r1.equals(r2)); // true
    }
}

Sources