Every Java object inherits the same basic toolbox, so interviewers use this question to check whether you know the foundation, not just the syntax.
Question: What methods are available in Object class?
Answer: The Object class is the root of all Java classes, so every object gets its methods from it. The main methods are equals(), hashCode(), toString(), getClass(), clone(), wait() in three forms, notify(), notifyAll(), and the deprecated finalize(). Some are for identity, some for debugging, some for copying, and some for thread coordination.
Interview-Ready Answer: In Java, Object provides the methods that every class inherits: equals, hashCode, toString, getClass, clone, wait with three overloads, notify, notifyAll, and finalize, which is deprecated. I usually group them as identity methods, cloning, and monitor methods for thread coordination. One important detail I mention is that wait and notify must be called while holding the same object's monitor, otherwise Java throws IllegalMonitorStateException.
Object really isEvery class in Java implicitly extends Object, so these methods are the universal baseline. Think of them as the smallest contract the JVM gives to every instance: identity, readable text, copying, and thread coordination.
getClass() returns the runtime class, not the declared variable type. equals() answers semantic equality, while hashCode() gives a fast integer used by hash tables like HashMap and HashSet.toString() turns an object into a human-readable line. The default form is ClassName@hexHash, but good classes override it to show real data.clone() makes a field-by-field shallow copy. It only works if the class implements the marker interface Cloneable; otherwise super.clone() throws CloneNotSupportedException.wait(), notify(), and notifyAll() use an object's monitor, meaning its built-in lock. A thread must own that monitor by entering a synchronized block on the same object; then wait() releases the monitor and parks the thread, while notify() or notifyAll() wake waiting threads so they can compete to reacquire the lock.finalize() was once a GC hook, but it is deprecated and should not be used for resource cleanup. Modern Java uses try-with-resources or Cleaner instead.| Family | Methods | Main purpose | Gotcha |
|---|---|---|---|
| Identity | equals, hashCode | Compare objects | Override together |
| Type/Text | getClass, toString | Inspect or print | Default text is not memory address |
| Copying | clone | Make a copy | Usually shallow |
| Concurrency | wait, notify, notifyAll | Coordinate threads | Must hold monitor |
| Cleanup | finalize | GC callback | Deprecated |
| Method | Wakes | Best use | Risk |
|---|---|---|---|
| notify() | One thread | One waiter, one condition | May wake the wrong waiter |
| notifyAll() | All waiters | Many waiters or mixed conditions | More context switching |
equals and hashCode when objects represent the same business value, such as two Order objects with the same id.toString for logs, debugging, and error messages; do not rely on the default output in production because it is too vague.getClass when you need exact runtime type checks, for example in framework code or strict equality logic.wait/notify only for low-level coordination. In modern code, higher-level tools like BlockingQueue, CountDownLatch, or Condition are usually safer.clone rarely. A copy constructor or factory method is often clearer and avoids the shallow-copy trap.Most of these methods are effectively constant time, O(1), in normal use: getClass(), equals(), hashCode(), and toString() usually complete in nanoseconds to a few dozen nanoseconds on a warmed-up JVM. clone() is typically O(n) in the number of fields copied, and for arrays it is linear in array length. wait() is different: its call cost is tiny, but the wall-clock time can be milliseconds, seconds, or forever because it depends on another thread, the scheduler, and interrupts.
Important edge cases: wait() with no timeout can block indefinitely; wait(long, int) rejects invalid nanosecond values; notify() wakes one arbitrary waiting thread, so if multiple threads are waiting for different conditions, notifyAll() is often safer. Also, toString() uses the current hashCode() result, so if you override hashCode(), the default text changes too.
Real-world story: Imagine an e-commerce checkout service with a background inventory worker. The worker thread waits for a stock update on a shared queue object, and the producer thread calls notifyAll() after reserving items. This is the kind of place where the Object methods matter: equals helps deduplicate order requests, hashCode lets the request live in a HashSet, toString() makes logs readable, and wait/notify keep threads from burning CPU in a busy loop.
What goes wrong if someone misunderstands the API? A developer moves wait() outside the synchronized block, or uses notify() where multiple consumer threads are waiting. In production, one bug throws IllegalMonitorStateException immediately; the other is sneakier: some workers never wake up, checkout latency climbs, and logs show threads stuck in WAITING on java.lang.Object.wait. Customers see cart timeouts even though the service is still running.
import java.util.Objects;
public class ObjectMethodsDemo {
static class Person implements Cloneable {
private final int id;
private String name;
Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true; // same reference: always equal
}
if (!(other instanceof Person)) {
return false; // different type: not equal
}
Person that = (Person) other;
return id == that.id && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
@Override
public String toString() {
return "Person{id=" + id + ", name='" + name + "'}";
}
@Override
protected Person clone() {
try {
return (Person) super.clone(); // shallow copy; nested mutable objects would still be shared
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // impossible because we implement Cloneable
}
}
}
public static void main(String[] args) throws InterruptedException {
Person p1 = new Person(7, "Mina");
Person p2 = new Person(7, "Mina");
System.out.println("getClass(): " + p1.getClass().getName());
System.out.println("equals(): " + p1.equals(p2));
System.out.println("hashCode(): " + p1.hashCode() + " / " + p2.hashCode());
System.out.println("toString(): " + p1);
Person copy = p1.clone();
System.out.println("clone() returns same reference? " + (copy == p1));
System.out.println("clone() equals original? " + copy.equals(p1));
Object lock = new Object();
String[] box = new String[1];
Thread waiter = new Thread(() -> {
synchronized (lock) {
while (box[0] == null) {
try {
System.out.println("waiter: waiting for a message...");
lock.wait(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
System.out.println("waiter: received -> " + box[0]);
}
});
Thread notifier = new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized (lock) {
box[0] = "job finished";
lock.notifyAll();
}
});
waiter.start();
notifier.start();
waiter.join();
notifier.join();
// Edge case: wait() without owning the monitor fails immediately.
try {
lock.wait(1);
} catch (IllegalMonitorStateException e) {
System.out.println("edge case: " + e.getClass().getSimpleName() + " when calling wait() outside synchronized");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}Follow-up & Tricky Questions:
equals() and hashCode() be overridden together? Hash-based collections use hashCode() to choose a bucket and equals() to find the exact match inside it. If equal objects have different hash codes, lookups and inserts become inconsistent.getClass() return? It returns the runtime class object of the instance, as a Class<?> value. It is final, so you cannot override it.clone() protected and why does it need Cloneable? Object.clone() is protected because cloning is not meant to be automatic for every class. The Cloneable interface is a marker interface, meaning it has no methods and only signals that super.clone() is allowed.notifyAll() instead of notify()? Use notifyAll() when multiple threads may wait for different conditions on the same lock. It wakes everyone so each thread can re-check its condition, which is safer than waking a random one.finalize() still mentioned if it is deprecated? Interviewers ask it because old code may still contain it, but modern Java should not rely on it. Resource cleanup belongs in try-with-resources or a Cleaner.Object.toString() print a memory address? No. It prints the class name and a hexadecimal form of the object's hash code, which is not guaranteed to be a real address.wait() release all locks? No. It releases only the monitor of the object you called it on; any other locks the thread already holds remain held.wait() or notify() without synchronized? No. The current thread must own the same object's monitor, or Java throws IllegalMonitorStateException.Common Mistakes:
equals, hashCode, and toString. Correction: also remember getClass, clone, wait, notify, notifyAll, and deprecated finalize.toString() shows a memory address. Correction: it shows class name plus a hex hash value, which may come from an overridden hashCode().equals() but not hashCode(). Correction: the two methods must stay consistent, especially for HashMap and HashSet.wait() or notify() outside synchronized. Correction: you must own the object's monitor first.Memory Hook: Think of Object as a default toolbox: an ID badge (getClass), a fingerprint (equals/hashCode), a name tag (toString), a copy button (clone), and an office bell (wait/notify).
Cheat Sheet:
equals compares logical equality.hashCode supports hash-based collections.toString gives readable debug text.getClass returns the runtime class.clone is shallow and depends on Cloneable.wait, notify, and notifyAll are monitor methods for thread coordination.Practice Tasks:
Book class and override equals, hashCode, and toString.wait() and notifyAll().toString() of a plain Object, then override toString() in your own class and compare the output.