Object

About

The Object class is the root of the class hierarchy in Java. Every class in Java directly or indirectly inherits from the Object class. It provides basic methods that all Java objects share, such as equality, hashing, cloning, and string representation. It resides in the java.lang package, and no import is required to use it.

Features

  1. Root Class: The base class for all Java classes.

  2. Essential Methods: Provides methods for object comparison, hashing, string conversion, thread synchronization, and more.

  3. Automatic Inheritance: All classes inherit from Object either directly or through other classes.

  4. Used in Collections: The Object class serves as a common data type in generic structures like List<Object>.

Internal Working

1. Inheritance and Subtyping

  • Any class can be cast to Object.

  • This allows collections and frameworks to handle objects generically.

2. Methods Overview

The Object class defines several methods that are fundamental to Java programming:

  • equals: Determines logical equality.

  • hashCode: Returns a hash code for the object.

  • toString: Provides a string representation of the object.

  • clone: Creates a shallow copy of the object.

  • finalize: Performs cleanup before garbage collection.

  • wait, notify, notifyAll: Used for thread synchronization.

Key Methods

Key Method

Description

equals(Object obj)

Compares the current object with the specified object for equality. Default Implementation: Compares references (==).

hashCode()

Returns an integer hash code representing the object. Contract with equals: If two objects are equal, they must have the same hash code.

toString()

Returns a string representation of the object. Default format: ClassName@HashCode.

clone()

Creates a shallow copy of the object. Requirement: Must implement Cloneable interface; otherwise, throws CloneNotSupportedException.

wait(), notify(), notifyAll()

Thread synchronization methods for inter-thread communication. Usage: Used within synchronized blocks or methods.

finalize()

Called by the garbage collector before reclaiming memory. Note: Deprecated due to unreliability and better alternatives like try-with-resources.

Limitations

  1. Default Behavior: The default implementations of methods like equals and hashCode may not be suitable for all use cases and often need overriding.

  2. Synchronization Overhead: Methods like wait and notify require careful handling to avoid deadlocks.

  3. Deprecated Finalize: finalize is unreliable and not recommended for resource cleanup.

Real-World Usage

  1. Equality and Hashing: Custom classes that use collections like HashSet or HashMap often override equals and hashCode.

  2. Debugging: toString is commonly overridden to provide meaningful information about objects during debugging.

  3. Concurrency: Thread-safe communication using wait, notify, and notifyAll.

  4. Generic Object Handling: Used in APIs and frameworks that work with objects generically (e.g., Object[], Reflection).

Examples

1. Default Methods

public class ObjectExample {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();

        // toString
        System.out.println(obj1.toString()); // Output: java.lang.Object@<hashcode>

        // equals
        System.out.println(obj1.equals(obj2)); // Output: false

        // hashCode
        System.out.println(obj1.hashCode()); // Output: <hashcode>
    }
}

2. Overriding equals and hashCode

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return 31 * name.hashCode() + age;
    }
}

public class EqualsHashCodeExample {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Alice", 30);

        System.out.println(p1.equals(p2)); // Output: true
        System.out.println(p1.hashCode() == p2.hashCode()); // Output: true
    }
}

3. Using clone

class CloneableExample implements Cloneable {
    int value;

    public CloneableExample(int value) {
        this.value = value;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class CloneExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableExample original = new CloneableExample(10);
        CloneableExample copy = (CloneableExample) original.clone();

        System.out.println(original.value); // Output: 10
        System.out.println(copy.value); // Output: 10
    }
}

4. Synchronization with wait and notify

class WaitNotifyExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread waitingThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Waiting..."); // Output: Waiting...
                    lock.wait();
                    System.out.println("Notified!"); // Output: Notified!
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread notifyingThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Notifying..."); // Output: Notifying...
                lock.notify();
            }
        });

        waitingThread.start();
        notifyingThread.start();
    }
}

Last updated

Was this helpful?