SyntaxStudy
Sign Up
Java The Object Class, equals, hashCode, and toString
Java Beginner 1 min read

The Object Class, equals, hashCode, and toString

Every class in Java implicitly extends java.lang.Object, which provides default implementations of several methods including equals, hashCode, toString, getClass, and clone. The default equals method tests reference equality (same object), which is rarely the right behaviour for value objects. The default toString returns a hex representation of the object address, which is not useful for debugging. Overriding equals requires following the contract: it must be reflexive, symmetric, transitive, consistent, and return false for null. Whenever you override equals you must also override hashCode, because objects that are equal must have the same hash code. Collections such as HashMap and HashSet rely on this contract to work correctly. The toString method should return a human-readable representation of the object that includes all relevant fields. IDEs can generate these methods automatically, and libraries such as Lombok can generate them via annotations. Java 14 introduced records, which are immutable data carriers that automatically generate equals, hashCode, and toString based on their components.
Example
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class ObjectMethods {

    static class Point {
        private final int x;
        private final int y;

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

        public int getX() { return x; }
        public int getY() { return y; }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;             // same reference
            if (!(o instanceof Point)) return false; // null or wrong type
            Point other = (Point) o;
            return x == other.x && y == other.y;
        }

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

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

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

        // Reference equality vs value equality
        System.out.println("p1 == p2      : " + (p1 == p2));       // false
        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

        // hashCode consistency
        System.out.println("p1.hashCode() == p2.hashCode(): "
            + (p1.hashCode() == p2.hashCode()));  // true

        // HashMap relies on correct equals/hashCode
        Map<Point, String> map = new HashMap<>();
        map.put(p1, "origin-ish");
        System.out.println("map.get(p2): " + map.get(p2)); // "origin-ish"

        // toString
        System.out.println(p1);       // Point(3, 4)

        // Java 16+ record for comparison
        // record PointRecord(int x, int y) {}
        // auto-generates equals, hashCode, toString, accessors
    }
}