Introduction
Imagine you're building with LEGO blocks. Every single LEGO piece, regardless of its shape, color, or size, has some fundamental properties—they can connect to other pieces, they have a certain weight, and they're made of the same plastic material. In Java, the Object class is exactly like this foundation—it's the superclass of every other class, and every Java object inherits 11 fundamental methods from it!
The Object class is the root of the class hierarchy in Java. Every class you create automatically extends Object (even if you don't write extends Object), which means all your objects come with these built-in capabilities out of the box.
What is the Object Class?
The Object class is the superclass of all other classes in Java. It's located in the java.lang package and provides basic methods that are common to all objects.
Key Characteristics:
- ✅ Root class - Every class implicitly extends Object
- ✅ 11 methods - Provides fundamental object behaviors
- ✅ Automatic inheritance - No need to explicitly extend
- ✅ Foundation - Essential for Java's object system
The 11 Object Class Methods
Here are all the methods provided by the Object class:
- toString() - String representation of object
- equals(Object obj) - Compares objects for equality
- hashCode() - Returns hash code value
- getClass() - Returns runtime class of object
- clone() - Creates and returns a copy
- finalize() - Called by garbage collector before destruction
- wait() - Makes thread wait for notification
- wait(long timeout) - Wait with timeout
- wait(long timeout, int nanos) - Wait with nanosecond precision
- notify() - Wakes up a waiting thread
- notifyAll() - Wakes up all waiting threads
Code Explanation with Examples
Example 1: toString() Method
class Student {
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.gpa = gpa;
}
// ❌ Without toString() override - uses Object's default
// Default: className@hashCode
// ✅ With toString() override - provides meaningful representation
@Override
public String toString() {
return String.format("Student{name='%s', age=%d, gpa=%.2f}", name, age, gpa);
}
}
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
// No toString() override - will use Object's default
}
public class ToStringDemo {
public static void main(String[] args) {
System.out.println("=== toString() METHOD DEMO ===");
Student student = new Student("Alice", 20, 3.8);
Product product = new Product("Laptop", 999.99);
// Explicit toString() calls
System.out.println("Student toString(): " + student.toString());
System.out.println("Product toString(): " + product.toString());
// Implicit toString() calls (automatically called by print methods)
System.out.println("\n=== IMPLICIT toString() CALLS ===");
System.out.println("Student: " + student); // Automatically calls toString()
System.out.println("Product: " + product); // Automatically calls toString()
// In collections
System.out.println("\n=== IN COLLECTIONS ===");
java.util.List<Object> items = new java.util.ArrayList<>();
items.add(student);
items.add(product);
System.out.println("List contents:");
for (Object item : items) {
System.out.println(" - " + item); // Automatically calls toString()
}
// Object's default toString() format
System.out.println("\n=== DEFAULT toString() FORMAT ===");
System.out.println("Default format: ClassName@HashCode");
System.out.println("Student class: " + student.getClass().getSimpleName());
System.out.println("Student hashCode: " + Integer.toHexString(student.hashCode()));
}
}
Output:
=== toString() METHOD DEMO ===
Student toString(): Student{name='Alice', age=20, gpa=3.80}
Product toString(): Product@15db9742
=== IMPLICIT toString() CALLS ===
Student: Student{name='Alice', age=20, gpa=3.80}
Product: Product@15db9742
=== IN COLLECTIONS ===
List contents:
- Student{name='Alice', age=20, gpa=3.80}
- Product@15db9742
=== DEFAULT toString() FORMAT ===
Default format: ClassName@HashCode
Student class: Student
Student hashCode: 6d311334
Example 2: equals() and hashCode() Methods
class Person {
private String id;
private String name;
private int age;
public Person(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
// ❌ Without equals() override - uses Object's equals() (reference comparison)
// ❌ Without hashCode() override - uses Object's hashCode() (memory address based)
// ✅ Proper equals() override
@Override
public boolean equals(Object obj) {
// 1. Check if same object reference
if (this == obj) return true;
// 2. Check if null or different class
if (obj == null || getClass() != obj.getClass()) return false;
// 3. Cast to Person
Person person = (Person) obj;
// 4. Compare significant fields
return age == person.age &&
id.equals(person.id) &&
name.equals(person.name);
}
// ✅ Proper hashCode() override (MUST be consistent with equals())
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return String.format("Person{id='%s', name='%s', age=%d}", id, name, age);
}
}
class BadPerson {
private String id;
private String name;
public BadPerson(String id, String name) {
this.id = id;
this.name = name;
}
// ❌ BAD: Overrides equals() but not hashCode() - breaks contract!
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
BadPerson that = (BadPerson) obj;
return id.equals(that.id) && name.equals(that.name);
}
// ❌ MISSING: No hashCode() override!
}
public class EqualsHashCodeDemo {
public static void main(String[] args) {
System.out.println("=== equals() AND hashCode() DEMO ===");
Person person1 = new Person("001", "Alice", 25);
Person person2 = new Person("001", "Alice", 25);
Person person3 = new Person("002", "Bob", 30);
System.out.println("person1: " + person1);
System.out.println("person2: " + person2);
System.out.println("person3: " + person3);
System.out.println("\n=== EQUALS COMPARISON ===");
System.out.println("person1 == person2: " + (person1 == person2)); // Reference comparison
System.out.println("person1.equals(person2): " + person1.equals(person2)); // Content comparison
System.out.println("person1.equals(person3): " + person1.equals(person3));
System.out.println("\n=== HASHCODE VALUES ===");
System.out.println("person1.hashCode(): " + person1.hashCode());
System.out.println("person2.hashCode(): " + person2.hashCode());
System.out.println("person3.hashCode(): " + person3.hashCode());
System.out.println("Hash codes equal for equal objects: " +
(person1.hashCode() == person2.hashCode()));
System.out.println("\n=== USING IN COLLECTIONS ===");
java.util.Set<Person> personSet = new java.util.HashSet<>();
personSet.add(person1);
personSet.add(person2); // Should not be added (duplicate)
personSet.add(person3);
System.out.println("HashSet size: " + personSet.size()); // Should be 2
System.out.println("HashSet contents: " + personSet);
System.out.println("\n=== PROBLEM: EQUALS WITHOUT HASHCODE ===");
BadPerson bad1 = new BadPerson("001", "Alice");
BadPerson bad2 = new BadPerson("001", "Alice");
System.out.println("bad1.equals(bad2): " + bad1.equals(bad2));
System.out.println("bad1.hashCode(): " + bad1.hashCode());
System.out.println("bad2.hashCode(): " + bad2.hashCode());
System.out.println("Different hashcodes for equal objects! ❌");
java.util.Set<BadPerson> badSet = new java.util.HashSet<>();
badSet.add(bad1);
badSet.add(bad2); // Both added! ❌ Broken contract
System.out.println("Bad HashSet size: " + badSet.size()); // Should be 1, but is 2!
System.out.println("\n=== EQUALS CONTRACT RULES ===");
System.out.println("1. Reflexive: x.equals(x) must be true");
System.out.println("2. Symmetric: x.equals(y) ⇔ y.equals(x)");
System.out.println("3. Transitive: x.equals(y) ∧ y.equals(z) ⇒ x.equals(z)");
System.out.println("4. Consistent: Multiple calls return same result");
System.out.println("5. x.equals(null) must return false");
System.out.println("6. If x.equals(y), then x.hashCode() == y.hashCode()");
}
}
Output:
=== equals() AND hashCode() DEMO ===
person1: Person{id='001', name='Alice', age=25}
person2: Person{id='001', name='Alice', age=25}
person3: Person{id='002', name='Bob', age=30}
=== EQUALS COMPARISON ===
person1 == person2: false
person1.equals(person2): true
person1.equals(person3): false
=== HASHCODE VALUES ===
person1.hashCode(): 1965783557
person2.hashCode(): 1965783557
person3.hashCode(): 66481
Hash codes equal for equal objects: true
=== USING IN COLLECTIONS ===
HashSet size: 2
HashSet contents: [Person{id='002', name='Bob', age=30}, Person{id='001', name='Alice', age=25}]
=== PROBLEM: EQUALS WITHOUT HASHCODE ===
bad1.equals(bad2): true
bad1.hashCode(): 366712642
bad2.hashCode(): 1829164700
Different hashcodes for equal objects! ❌
Bad HashSet size: 2
=== EQUALS CONTRACT RULES ===
1. Reflexive: x.equals(x) must be true
2. Symmetric: x.equals(y) ⇔ y.equals(x)
3. Transitive: x.equals(y) ∧ y.equals(z) ⇒ x.equals(z)
4. Consistent: Multiple calls return same result
5. x.equals(null) must return false
6. If x.equals(y), then x.hashCode() == y.hashCode()
Example 3: getClass() and clone() Methods
class Employee implements Cloneable {
private String name;
private int employeeId;
private double salary;
private Department department;
public Employee(String name, int employeeId, double salary, Department department) {
this.name = name;
this.employeeId = employeeId;
this.salary = salary;
this.department = department;
}
// getClass() demonstration
public void displayClassInfo() {
Class<?> clazz = this.getClass();
System.out.println("Class Name: " + clazz.getName());
System.out.println("Simple Name: " + clazz.getSimpleName());
System.out.println("Superclass: " + clazz.getSuperclass().getSimpleName());
System.out.println("Is Interface: " + clazz.isInterface());
}
// ✅ Proper clone() implementation
@Override
protected Object clone() throws CloneNotSupportedException {
// First, call super.clone() for shallow copy
Employee cloned = (Employee) super.clone();
// For deep copy, also clone mutable fields
cloned.department = (Department) department.clone();
return cloned;
}
// Alternative: Copy constructor for cloning
public Employee(Employee other) {
this.name = other.name;
this.employeeId = other.employeeId;
this.salary = other.salary;
this.department = new Department(other.department);
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getEmployeeId() { return employeeId; }
public double getSalary() { return salary; }
public void setSalary(double salary) { this.salary = salary; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
@Override
public String toString() {
return String.format("Employee{id=%d, name='%s', salary=%.2f, dept=%s}",
employeeId, name, salary, department);
}
}
class Department implements Cloneable {
private String name;
private String location;
public Department(String name, String location) {
this.name = name;
this.location = location;
}
// Copy constructor
public Department(Department other) {
this.name = other.name;
this.location = other.location;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // Shallow copy is fine for Department (Strings are immutable)
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
@Override
public String toString() {
return String.format("Department{name='%s', location='%s'}", name, location);
}
}
class Manager extends Employee {
private int teamSize;
public Manager(String name, int employeeId, double salary, Department department, int teamSize) {
super(name, employeeId, salary, department);
this.teamSize = teamSize;
}
@Override
public void displayClassInfo() {
super.displayClassInfo();
System.out.println("Additional info: This is a Manager class");
}
@Override
public String toString() {
return super.toString().replace("Employee", "Manager") +
String.format(", teamSize=%d}", teamSize);
}
}
public class GetClassCloneDemo {
public static void main(String[] args) {
System.out.println("=== getClass() AND clone() DEMO ===");
Department dept = new Department("Engineering", "New York");
Employee emp1 = new Employee("John Doe", 1001, 75000.0, dept);
Manager mgr1 = new Manager("Alice Smith", 2001, 120000.0, dept, 8);
System.out.println("=== getClass() DEMONSTRATION ===");
System.out.println("Employee class info:");
emp1.displayClassInfo();
System.out.println("\nManager class info:");
mgr1.displayClassInfo();
System.out.println("\n=== CLASS COMPARISON ===");
System.out.println("emp1.getClass() == Employee.class: " +
(emp1.getClass() == Employee.class));
System.out.println("mgr1.getClass() == Manager.class: " +
(mgr1.getClass() == Manager.class));
System.out.println("mgr1.getClass() == Employee.class: " +
(mgr1.getClass() == Employee.class));
System.out.println("mgr1 instanceof Employee: " + (mgr1 instanceof Employee));
System.out.println("\n=== clone() DEMONSTRATION ===");
try {
// Clone using clone() method
Employee emp2 = (Employee) emp1.clone();
System.out.println("Original: " + emp1);
System.out.println("Cloned: " + emp2);
// Modify original and check if clone is affected
emp1.setSalary(80000.0);
emp1.getDepartment().setName("Modified Engineering");
System.out.println("\nAfter modifying original:");
System.out.println("Original: " + emp1);
System.out.println("Cloned: " + emp2); // Should be unchanged (deep copy)
System.out.println("\n=== COPY CONSTRUCTOR ===");
// Alternative to clone() - copy constructor
Employee emp3 = new Employee(emp1);
System.out.println("Original: " + emp1);
System.out.println("Copied: " + emp3);
} catch (CloneNotSupportedException e) {
System.out.println("Clone not supported: " + e.getMessage());
}
System.out.println("\n=== OBJECT CLASS HIERARCHY ===");
displayClassHierarchy(mgr1);
}
public static void displayClassHierarchy(Object obj) {
System.out.println("Class hierarchy for: " + obj.getClass().getSimpleName());
Class<?> clazz = obj.getClass();
while (clazz != null) {
System.out.println(" → " + clazz.getName());
clazz = clazz.getSuperclass();
}
System.out.println(" → (end)");
}
}
Output:
=== getClass() AND clone() DEMO ===
=== getClass() DEMONSTRATION ===
Employee class info:
Class Name: Employee
Simple Name: Employee
Superclass: Object
Is Interface: false
Manager class info:
Class Name: Manager
Simple Name: Manager
Superclass: Employee
Is Interface: false
Additional info: This is a Manager class
=== CLASS COMPARISON ===
emp1.getClass() == Employee.class: true
mgr1.getClass() == Manager.class: true
mgr1.getClass() == Employee.class: false
mgr1 instanceof Employee: true
=== clone() DEMONSTRATION ===
Original: Employee{id=1001, name='John Doe', salary=75000.00, dept=Department{name='Engineering', location='New York'}}
Cloned: Employee{id=1001, name='John Doe', salary=75000.00, dept=Department{name='Engineering', location='New York'}}
After modifying original:
Original: Employee{id=1001, name='John Doe', salary=80000.00, dept=Department{name='Modified Engineering', location='New York'}}
Cloned: Employee{id=1001, name='John Doe', salary=75000.00, dept=Department{name='Engineering', location='New York'}}
=== COPY CONSTRUCTOR ===
Original: Employee{id=1001, name='John Doe', salary=80000.00, dept=Department{name='Modified Engineering', location='New York'}}
Copied: Employee{id=1001, name='John Doe', salary=80000.00, dept=Department{name='Modified Engineering', location='New York'}}
=== OBJECT CLASS HIERARCHY ===
Class hierarchy for: Manager
→ Manager
→ Employee
→ java.lang.Object
→ (end)
Example 4: wait(), notify(), notifyAll() Methods
class Message {
private String content;
private boolean empty = true;
public synchronized String read() {
// Wait until message is available
while (empty) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting to read...");
wait(); // 🎯 Object's wait() method
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
empty = true;
System.out.println(Thread.currentThread().getName() + " read: " + content);
notifyAll(); // 🎯 Object's notifyAll() method
return content;
}
public synchronized void write(String content) {
// Wait until message has been read
while (!empty) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting to write...");
wait(); // 🎯 Object's wait() method
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
empty = false;
this.content = content;
System.out.println(Thread.currentThread().getName() + " wrote: " + content);
notifyAll(); // 🎯 Object's notifyAll() method
}
}
class Reader implements Runnable {
private Message message;
public Reader(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
String readMessage = message.read();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
class Writer implements Runnable {
private Message message;
public Writer(Message message) {
this.message = message;
}
@Override
public void run() {
String[] messages = {
"Hello World!",
"Java Programming",
"Thread Communication"
};
for (String msg : messages) {
message.write(msg);
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
public class WaitNotifyDemo {
public static void main(String[] args) {
System.out.println("=== wait(), notify(), notifyAll() DEMO ===");
System.out.println("Producer-Consumer Problem Solution\n");
Message message = new Message();
// Create multiple readers and writers
Thread writerThread = new Thread(new Writer(message), "Writer-1");
Thread readerThread1 = new Thread(new Reader(message), "Reader-1");
Thread readerThread2 = new Thread(new Reader(message), "Reader-2");
writerThread.start();
readerThread1.start();
readerThread2.start();
// Wait for threads to complete
try {
writerThread.join();
readerThread1.join();
readerThread2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("\n=== THREAD COMMUNICATION COMPLETED ===");
// Demonstrate simple wait/notify
System.out.println("\n=== SIMPLE WAIT/NOTIFY EXAMPLE ===");
Object lock = new Object();
Thread waitingThread = new Thread(() -> {
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + " is waiting...");
lock.wait(); // 🎯 Object's wait() method
System.out.println(Thread.currentThread().getName() + " is notified!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "WaitingThread");
Thread notifyingThread = new Thread(() -> {
synchronized (lock) {
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " is notifying...");
lock.notify(); // 🎯 Object's notify() method
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "NotifyingThread");
waitingThread.start();
notifyingThread.start();
try {
waitingThread.join();
notifyingThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("=== WAIT/NOTIFY COMPLETED ===");
}
}
Output:
=== wait(), notify(), notifyAll() DEMO === Producer-Consumer Problem Solution Writer-1 wrote: Hello World! Reader-2 read: Hello World! Writer-1 is waiting to write... Reader-1 is waiting to read... Writer-1 wrote: Java Programming Reader-1 read: Java Programming Writer-1 is waiting to write... Reader-2 is waiting to read... Writer-1 wrote: Thread Communication Reader-2 read: Thread Communication === THREAD COMMUNICATION COMPLETED === === SIMPLE WAIT/NOTIFY EXAMPLE === WaitingThread is waiting... NotifyingThread is notifying... WaitingThread is notified! === WAIT/NOTIFY COMPLETED ===
Example 5: finalize() Method (Deprecated but Important to Understand)
class Resource {
private String name;
private static int created = 0;
private static int finalized = 0;
public Resource(String name) {
this.name = name;
created++;
System.out.println("Resource '" + name + "' created. Total created: " + created);
}
public void use() {
System.out.println("Using resource: " + name);
}
// 🎯 finalize() method - called by garbage collector before object destruction
// ⚠️ DEPRECATED in Java 9+ - don't use in new code!
@Override
protected void finalize() throws Throwable {
try {
finalized++;
System.out.println("⚠️ Resource '" + name + "' finalized. Total finalized: " + finalized);
} finally {
super.finalize();
}
}
public static void displayStats() {
System.out.println("=== RESOURCE STATS ===");
System.out.println("Created: " + created);
System.out.println("Finalized: " + finalized);
System.out.println("Active: " + (created - finalized));
}
}
public class FinalizeDemo {
public static void main(String[] args) {
System.out.println("=== finalize() METHOD DEMO ===");
System.out.println("⚠️ Note: finalize() is deprecated in modern Java!");
System.out.println("It's shown here for educational purposes only.\n");
// Create some resources
Resource res1 = new Resource("Database Connection");
Resource res2 = new Resource("File Handle");
Resource res3 = new Resource("Network Socket");
// Use resources
res1.use();
res2.use();
// Make objects eligible for garbage collection
System.out.println("\n=== MAKING OBJECTS ELIGIBLE FOR GC ===");
res1 = null;
res2 = null;
// Explicitly suggest garbage collection (not guaranteed!)
System.out.println("Suggesting garbage collection...");
System.gc();
// Wait a bit to allow GC to run (not reliable in demo)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Resource.displayStats();
// Create more resources
System.out.println("\n=== CREATING MORE RESOURCES ===");
for (int i = 0; i < 5; i++) {
new Resource("TempResource-" + i);
}
// Suggest GC again
System.out.println("Suggesting garbage collection again...");
System.gc();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Resource.displayStats();
System.out.println("\n=== MODERN ALTERNATIVES TO finalize() ===");
System.out.println("1. try-with-resources (AutoCloseable interface)");
System.out.println("2. Cleaner API (Java 9+)");
System.out.println("3. Explicit close() methods");
System.out.println("4. Phantom references");
// Modern approach: try-with-resources
System.out.println("\n=== MODERN APPROACH: try-with-resources ===");
try (ModernResource mr = new ModernResource("Modern Resource")) {
mr.use();
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
// Automatically closed - no need for finalize()
}
}
// Modern approach using AutoCloseable
class ModernResource implements AutoCloseable {
private String name;
private boolean closed = false;
public ModernResource(String name) {
this.name = name;
System.out.println("ModernResource '" + name + "' created");
}
public void use() {
if (closed) {
throw new IllegalStateException("Resource is closed");
}
System.out.println("Using modern resource: " + name);
}
@Override
public void close() {
if (!closed) {
closed = true;
System.out.println("ModernResource '" + name + "' closed properly");
}
}
}
Output:
=== finalize() METHOD DEMO === ⚠️ Note: finalize() is deprecated in modern Java! It's shown here for educational purposes only. Resource 'Database Connection' created. Total created: 1 Resource 'File Handle' created. Total created: 2 Resource 'Network Socket' created. Total created: 3 Using resource: Database Connection Using resource: File Handle === MAKING OBJECTS ELIGIBLE FOR GC === Suggesting garbage collection... ⚠️ Resource 'File Handle' finalized. Total finalized: 1 ⚠️ Resource 'Database Connection' finalized. Total finalized: 2 === RESOURCE STATS === Created: 3 Finalized: 2 Active: 1 === CREATING MORE RESOURCES === Resource 'TempResource-0' created. Total created: 4 Resource 'TempResource-1' created. Total created: 5 Resource 'TempResource-2' created. Total created: 6 Resource 'TempResource-3' created. Total created: 7 Resource 'TempResource-4' created. Total created: 8 Suggesting garbage collection again... ⚠️ Resource 'TempResource-4' finalized. Total finalized: 3 ⚠️ Resource 'TempResource-3' finalized. Total finalized: 4 ⚠️ Resource 'TempResource-2' finalized. Total finalized: 5 ⚠️ Resource 'TempResource-1' finalized. Total finalized: 6 ⚠️ Resource 'TempResource-0' finalized. Total finalized: 7 === RESOURCE STATS === Created: 8 Finalized: 7 Active: 1 === MODERN ALTERNATIVES TO finalize() === 1. try-with-resources (AutoCloseable interface) 2. Cleaner API (Java 9+) 3. Explicit close() methods 4. Phantom references === MODERN APPROACH: try-with-resources === ModernResource 'Modern Resource' created Using modern resource: Modern Resource ModernResource 'Modern Resource' closed properly
Complete Object Methods Summary
| Method | Purpose | When to Override |
|---|---|---|
| toString() | String representation | Always for meaningful output |
| equals() | Object equality comparison | When you need content comparison |
| hashCode() | Hash code for collections | Must override when equals() is overridden |
| getClass() | Runtime class information | Rarely - used for reflection |
| clone() | Object copying | When you need cloning capability |
| finalize() | Cleanup before GC | ⚠️ Deprecated - don't use |
| wait() | Thread waiting | Rarely - used in synchronization |
| notify() | Thread notification | Rarely - used in synchronization |
| notifyAll() | Notify all threads | Rarely - used in synchronization |
Best Practices
✅ toString(): Always override for debugging and logging
✅ equals() and hashCode(): Override together, follow contract
✅ clone(): Prefer copy constructors or factory methods
✅ finalize(): ⚠️ Avoid - use try-with-resources instead
✅ wait/notify: Use higher-level concurrency utilities when possible
// ✅ GOOD: Proper equals() and hashCode()
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name) &&
Objects.equals(email, person.email);
}
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
// ✅ GOOD: Meaningful toString()
@Override
public String toString() {
return String.format("Person{name='%s', age=%d, email='%s'}", name, age, email);
}
Common Pitfalls
// ❌ BAD: equals() without hashCode()
class BadExample {
private String data;
@Override
public boolean equals(Object obj) { /*...*/ }
// ❌ Missing hashCode()!
}
// ❌ BAD: Relying on finalize()
class Resource {
// ❌ Don't rely on finalize() for cleanup
protected void finalize() { cleanup(); }
}
// ✅ GOOD: Use try-with-resources
try (Resource res = new Resource()) {
// use resource
} // automatically closed
Conclusion
The Object class is the foundation of Java's object system:
- ✅ Root of class hierarchy - every class extends Object
- ✅ 11 fundamental methods - common behaviors for all objects
- ✅ Automatic inheritance - no need to explicitly extend
- ✅ Essential for collections - equals(), hashCode(), toString()
- ✅ Thread coordination - wait(), notify(), notifyAll()
Key Takeaways:
- Always override toString() for meaningful object representation
- Override equals() and hashCode() together following the contract
- Avoid finalize() - use modern resource management
- Use getClass() for runtime type information
- Understand wait/notify for basic thread coordination
Mastering Object class methods is essential for writing correct, efficient, and maintainable Java code. They're the building blocks that make Java's object-oriented system work!