ZetCode

Java Object Class

Last modified: April 13, 2025

The java.lang.Object class is the root of the Java class hierarchy. Every class in Java is directly or indirectly derived from the Object class. Understanding Object class methods is fundamental to Java programming as they provide basic functionality to all objects.

The Object class defines several important methods that are inherited by all other classes. These include methods for object comparison, hash code generation, string representation, and object cloning. Knowing how to properly override these methods is crucial for writing correct and efficient Java code.

Object Class Methods

The Object class provides several methods that are common to all Java objects. These methods can be overridden by subclasses to provide specific behavior. The main methods include toString, equals, hashCode, clone, and getClass.

public class Object {
    public final Class<?> getClass() {...}
    public int hashCode() {...}
    public boolean equals(Object obj) {...}
    protected Object clone() throws CloneNotSupportedException {...}
    public String toString() {...}
    public final void notify() {...}
    public final void notifyAll() {...}
    public final void wait() throws InterruptedException {...}
    protected void finalize() throws Throwable {...}
}

The code above shows the main methods provided by the Object class. These methods form the foundation of object behavior in Java and are available to all objects.

toString Method

The toString method returns a string representation of the object. By default, it returns the class name followed by '@' and the object's hash code. This method is often overridden to provide more meaningful information.

Main.java
package com.zetcode;

class Person {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + "]";
    }
}

public class Main {

    public static void main(String[] args) {
        Person person = new Person("John Doe", 30);
        System.out.println(person.toString());
        System.out.println(person); // println automatically calls toString
    }
}

In this example, we override the toString method in the Person class to return a meaningful string representation. When we print the object, this custom string is displayed instead of the default implementation.

equals Method

The equals method compares two objects for equality. The default implementation simply checks if two references point to the same object. For meaningful comparison, this method should be overridden.

Main.java
package com.zetcode;

class Book {
    private String title;
    private String author;
    
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        
        Book book = (Book) obj;
        return title.equals(book.title) && author.equals(book.author);
    }
}

public class Main {

    public static void main(String[] args) {
        Book book1 = new Book("Java Basics", "John Smith");
        Book book2 = new Book("Java Basics", "John Smith");
        Book book3 = new Book("Advanced Java", "Jane Doe");
        
        System.out.println("book1 equals book2: " + book1.equals(book2));
        System.out.println("book1 equals book3: " + book1.equals(book3));
    }
}

This example demonstrates how to properly override the equals method. We compare Book objects based on their title and author fields rather than memory addresses. The method first checks for reference equality, null, and class type before comparing fields.

hashCode Method

The hashCode method returns an integer hash code value for the object. This method must be overridden whenever equals is overridden to maintain the general contract that equal objects must have equal hash codes.

Main.java
package com.zetcode;

class Student {
    private int id;
    private String name;
    
    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Student student = (Student) obj;
        return id == student.id && name.equals(student.name);
    }
    
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + id;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }
}

public class Main {

    public static void main(String[] args) {
        Student s1 = new Student(101, "Alice");
        Student s2 = new Student(101, "Alice");
        
        System.out.println("s1 hash: " + s1.hashCode());
        System.out.println("s2 hash: " + s2.hashCode());
        System.out.println("Equal objects same hash? " + 
                          (s1.hashCode() == s2.hashCode()));
    }
}

This example shows a proper implementation of hashCode that matches our equals implementation. We use a common algorithm that combines hash codes of individual fields using prime numbers to reduce collisions.

clone Method

The clone method creates and returns a copy of the object. To make a class cloneable, it must implement the Cloneable interface and override the clone method. The default implementation performs a shallow copy.

Main.java
package com.zetcode;

class Address implements Cloneable {
    private String city;
    private String street;
    
    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    public void setStreet(String street) {
        this.street = street;
    }
    
    @Override
    public String toString() {
        return city + ", " + street;
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr1 = new Address("New York", "5th Avenue");
        Address addr2 = (Address) addr1.clone();
        
        System.out.println("Original: " + addr1);
        System.out.println("Clone: " + addr2);
        
        addr2.setStreet("Broadway");
        System.out.println("\nAfter modification:");
        System.out.println("Original: " + addr1);
        System.out.println("Clone: " + addr2);
    }
}

This example demonstrates how to implement cloning. The Address class implements Cloneable and overrides clone. After cloning, modifying the clone's street doesn't affect the original, showing a proper shallow copy implementation.

getClass Method

The getClass method returns the runtime class of an object. This method is final and cannot be overridden. It's useful for reflection and runtime type checking.

Main.java
package com.zetcode;

class Animal {}
class Dog extends Animal {}

public class Main {

    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        
        System.out.println("animal class: " + animal.getClass());
        System.out.println("dog class: " + dog.getClass());
        System.out.println("dog superclass: " + dog.getClass().getSuperclass());
        
        if (dog instanceof Animal) {
            System.out.println("dog is an Animal");
        }
    }
}

This example shows how getClass returns the actual runtime class of an object. Even though dog is declared as Animal, getClass returns Dog. We also demonstrate the instanceof operator for type checking.

wait, notify, notifyAll Methods

The wait, notify, and notifyAll methods are used for thread synchronization. They must be called from within a synchronized context and are fundamental to Java's inter-thread communication.

Main.java
package com.zetcode;

class Message {
    private String content;
    private boolean empty = true;

    public synchronized String read() {
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }
        empty = true;
        notifyAll();
        return content;
    }

    public synchronized void write(String content) {
        while (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
        empty = false;
        this.content = content;
        notifyAll();
    }
}

public class Main {

    public static void main(String[] args) {
        Message message = new Message();

        // Writer thread
        new Thread(() -> {
            String[] messages = {"First", "Second", "Third"};
            for (String msg : messages) {
                message.write(msg);
                System.out.println("Written: " + msg);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return;
                }
            }
            message.write("DONE");
        }).start();

        // Reader thread
        new Thread(() -> {
            String msg;
            while (!"DONE".equals(msg = message.read())) {
                if (msg != null) {
                    System.out.println("Read: " + msg);
                }
            }
            System.out.println("Reader finished.");
        }).start();
    }
}

This example demonstrates thread communication using wait and notifyAll. The Message class coordinates between writer and reader threads. The writer sends messages and the reader consumes them until receiving "DONE". Proper synchronization ensures thread safety and correct communication.

Source

Java Object Class Documentation

In this article, we've covered all major methods of the Java Object class with practical examples. Understanding these methods is essential for proper Java development as they form the foundation of object behavior in the language.

Author

My name is Jan Bodnar, and I am a dedicated programmer with many years of experience in the field. I began writing programming articles in 2007 and have since authored over 1,400 articles and eight e-books. With more than eight years of teaching experience, I am committed to sharing my knowledge and helping others master programming concepts.

List all Java tutorials.