00:00

Object Class in Java – The Root of Every Class

Last updated: 6 min read

java.lang.Object is the root of the Java class hierarchy. Every class extends Object directly or indirectly, so every object, including arrays, inherits its methods. That is why you can call toString(), equals() or hashCode() on any object, and why a variable of type Object can refer to anything.

This tutorial explains each Object method, how and when to override toString, equals and hashCode, the contract between them, and the helper class java.util.Objects.

1. Methods of the Object Class

MethodPurposeOverride?
String toString()Text representation; default is ClassName@hexHashUsually
boolean equals(Object o)Logical equality; default is identity (==)For value objects
int hashCode()Hash for hash-based collectionsWhenever equals is overridden
Class<?> getClass()Runtime class of the objectNo (final)
Object clone()Field-by-field copy (needs Cloneable)Rarely
wait(), notify(), notifyAll()Thread coordination on the object's monitorNo (final)
void finalize()Old pre-GC hookNever (deprecated for removal)

2. Overriding toString, equals and hashCode

ObjectMethods.java
import java.util.*;

public class ObjectMethods {
    static class Plain { int id = 7; }

    static final class Product {
        private final String sku;
        private final String name;
        Product(String sku, String name) { this.sku = sku; this.name = name; }

        @Override public String toString() { return "Product[" + sku + ", " + name + "]"; }

        @Override public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Product other)) return false;
            return sku.equals(other.sku);                      // identity by business key
        }
        @Override public int hashCode() { return Objects.hash(sku); }
    }

    public static void main(String[] args) {
        Plain p = new Plain();
        System.out.println("default toString starts with class name: " + p.toString().startsWith("ObjectMethods$Plain@"));
        System.out.println("default equals is identity: " + p.equals(new Plain()));

        Product a = new Product("SKU-1", "Keyboard");
        Product b = new Product("SKU-1", "Keyboard (new box)");
        System.out.println(a);
        System.out.println("a.equals(b): " + a.equals(b));
        System.out.println("same hashCode: " + (a.hashCode() == b.hashCode()));
        System.out.println("HashSet size: " + new HashSet<>(List.of(a, b)).size());
        System.out.println("getClass(): " + a.getClass().getSimpleName());
    }
}
Output
default toString starts with class name: true
default equals is identity: false
Product[SKU-1, Keyboard]
a.equals(b): true
same hashCode: true
HashSet size: 1
getClass(): Product
  • The default toString() output is rarely useful; override it for logging and debugging.
  • The default equals() compares references. Override it when two different objects can represent the same value, here products with the same SKU.
  • Because equals and hashCode agree, the HashSet correctly keeps only one product.

3. The equals and hashCode Contract

equals must be:

  • Reflexive: x.equals(x) is true.
  • Symmetric: x.equals(y) equals y.equals(x).
  • Transitive: if x equals y and y equals z, then x equals z.
  • Consistent, and x.equals(null) is false.

And: equal objects must have equal hash codes. Breaking this makes hash-based collections fail:

EqualsHashCodeBug.java
import java.util.HashSet;
import java.util.Set;

public class EqualsHashCodeBug {
    static class Point {
        final int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
        @Override public boolean equals(Object o) {
            return o instanceof Point p && x == p.x && y == p.y;
        }
        // hashCode NOT overridden: equal points get different hash codes
    }

    record FixedPoint(int x, int y) { }   // records generate equals AND hashCode

    public static void main(String[] args) {
        Set<Point> points = new HashSet<>();
        points.add(new Point(1, 2));
        System.out.println("broken contains: " + points.contains(new Point(1, 2)));

        Set<FixedPoint> fixed = new HashSet<>();
        fixed.add(new FixedPoint(1, 2));
        System.out.println("record contains: " + fixed.contains(new FixedPoint(1, 2)));
    }
}
Output
broken contains: false
record contains: true

Records, IDEs and Lombok generate correct implementations. Unequal objects may share a hash code (a collision), but fewer collisions mean faster lookups.

4. getClass()

getClass() returns the Class object describing the object's runtime type, used for reflection, logging and type checks: obj.getClass().getName(), getSimpleName() or getSuperclass(). It is final and native.

5. clone(), wait/notify and finalize()

  • clone() is protected and works only with Cloneable; copy constructors are usually better. See Object Cloning.
  • wait(), notify(), notifyAll() coordinate threads using the monitor every object has. See Synchronization.
  • finalize() is deprecated for removal; use try-with-resources or Cleaner. See Garbage Collection.

6. The java.util.Objects Helper Class

Objects (Java 7) provides null-safe static helpers for the Object methods and argument checking:

ObjectsUtility.java
import java.util.Objects;

public class ObjectsUtility {
    public static void main(String[] args) {
        String a = null, b = "java";
        System.out.println("Objects.equals(null, \"java\")  : " + Objects.equals(a, b));
        System.out.println("Objects.hash(\"a\", 1) equal     : " + (Objects.hash("a", 1) == Objects.hash("a", 1)));
        System.out.println("Objects.toString(null, \"n/a\") : " + Objects.toString(a, "n/a"));
        System.out.println("requireNonNullElse            : " + Objects.requireNonNullElse(a, "default"));
        System.out.println("isNull / nonNull              : " + Objects.isNull(a) + " / " + Objects.nonNull(b));
        try {
            Objects.requireNonNull(a, "name must not be null");
        } catch (NullPointerException e) {
            System.out.println("requireNonNull                : " + e.getMessage());
        }
    }
}
Output
Objects.equals(null, "java")  : false
Objects.hash("a", 1) equal     : true
Objects.toString(null, "n/a") : n/a
requireNonNullElse            : default
isNull / nonNull              : true / true
requireNonNull                : name must not be null

7. Why Have a Common Root Class?

  • Collections and other generic code can store and handle any object.
  • Every object can be printed, compared and hashed in a uniform way.
  • Every object can serve as a lock for synchronized.

8. Summary

  • Object is the superclass of every Java class and array.
  • Override toString for readable output, and equals with hashCode for value equality.
  • Equal objects must have equal hash codes, or hash-based collections break.
  • getClass, wait and notify are final; finalize is deprecated.
  • java.util.Objects offers null-safe helpers.

Related Tutorials