Java Object Class
Java Object Class
Every class in Java — whether you write it, import it from a library, or use one from the standard library — silently extends java.lang.Object. You never declare this. The compiler adds it automatically whenever a class does not explicitly extend something else. It is the single root of Java's entire class hierarchy.
This matters practically because every object you ever create in Java carries eleven methods inherited from Object. Most developers use toString() and never think about the rest. Then they run into a bug where two objects that should be equal are not, or their objects disappear silently in a HashSet, or they cannot figure out why their wait() call throws IllegalMonitorStateException. Every one of those problems traces back to Object methods they did not understand.
The Object Class Hierarchy
java.lang.Object
|
+-- String
+-- Integer
+-- ArrayList
+-- YourCustomClass
+-- Every other Java class ever written
Every class implicitly extends Object when no other parent is declared.
Every Java object IS-A Object.
This single-root design is what makes polymorphic collections possible. A List<Object> can hold anything. Arrays.sort() can compare anything that implements Comparable. Reflection can inspect anything. It all works because everything shares one common ancestor.
All Methods of the Object Class
| Method | Return Type | Purpose |
|---|---|---|
toString() | String | Human-readable representation of the object |
equals(Object obj) | boolean | Logical equality comparison |
hashCode() | int | Integer hash code for use in hash-based collections |
getClass() | Class<?> | Runtime class of the object — cannot be overridden |
clone() | Object | Shallow field-by-field copy — requires Cloneable |
finalize() | void | Called by GC before object is collected — deprecated Java 9+ |
wait() | void | Causes thread to wait until notify() or notifyAll() |
wait(long timeout) | void | Wait with a timeout in milliseconds |
wait(long timeout, int nanos) | void | Wait with millisecond + nanosecond timeout |
notify() | void | Wakes one thread waiting on this object's monitor |
notifyAll() | void | Wakes all threads waiting on this object's monitor |
toString() — The Most Overridden Method
The default toString() implementation in Object returns ClassName@hexHashCode — a string that is technically accurate but completely useless for debugging. Every class that represents domain data should override it.
// File: ToStringDemo.java
public class ToStringDemo {
// Without override — gets the Object default
static class RawOrder {
String orderId;
double amount;
RawOrder(String id, double amt) { orderId = id; amount = amt; }
}
// With override — meaningful output
static class Order {
private final String orderId;
private final String customerId;
private final double amount;
private String status;
Order(String orderId, String customerId, double amount) {
this.orderId = orderId;
this.customerId = customerId;
this.amount = amount;
this.status = "PLACED";
}
public void setStatus(String status) { this.status = status; }
@Override
public String toString() {
return "Order{"
+ "id='" + orderId + '\''
+ ", customer='" + customerId + '\''
+ ", amount=Rs." + amount
+ ", status='" + status + '\''
+ '}';
}
}
public static void main(String[] args) {
RawOrder raw = new RawOrder("ORD-001", 1499.0);
Order ord = new Order("ORD-002", "CUST-501", 2999.0);
// Default Object.toString() — class name + hex hash code
System.out.println("Default toString : " + raw);
// Overridden toString() — readable and useful
System.out.println("Overridden : " + ord);
// toString() is called implicitly in string concatenation and print statements
System.out.println("In concatenation : Order is " + ord);
}
}Output:
Default toString : ToStringDemo$RawOrder@7852e922
Overridden : Order{id='ORD-002', customer='CUST-501', amount=Rs.2999.0, status='PLACED'}
In concatenation : Order is Order{id='ORD-002', customer='CUST-501', amount=Rs.2999.0, status='PLACED'}
toString() is called implicitly whenever an object is passed to System.out.println, used in string concatenation, or logged. A meaningful override makes debugging, logging, and error messages dramatically faster to read. A missing override means log files full of Order@1b6d3586 that tell you nothing.
equals() — Logical Equality
The default equals() in Object checks reference equality — it returns true only if both references point to the exact same object in memory. For domain objects where equality means "same data", this default is wrong.
// File: EqualsDemo.java
import java.util.Objects;
public class EqualsDemo {
static class ProductWithoutEquals {
String sku;
String name;
ProductWithoutEquals(String sku, String name) {
this.sku = sku; this.name = name;
}
}
static class Product {
private final String sku;
private final String name;
private final double price;
Product(String sku, String name, double price) {
this.sku = sku;
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // same reference
if (obj == null) return false; // null check
if (!(obj instanceof Product other)) return false; // type check + cast
// Two products are equal when they share the same SKU
return Objects.equals(this.sku, other.sku);
}
@Override
public int hashCode() {
return Objects.hash(sku); // must match equals — same field(s)
}
@Override
public String toString() {
return sku + " | " + name + " | Rs." + price;
}
}
public static void main(String[] args) {
ProductWithoutEquals p1 = new ProductWithoutEquals("SKU-101", "Headphones");
ProductWithoutEquals p2 = new ProductWithoutEquals("SKU-101", "Headphones");
System.out.println("Without equals override:");
System.out.println(" p1 == p2 : " + (p1 == p2)); // false — different refs
System.out.println(" p1.equals(p2) : " + p1.equals(p2)); // false — Object default
Product prod1 = new Product("SKU-201", "Wireless Mouse", 1299.0);
Product prod2 = new Product("SKU-201", "Wireless Mouse", 1299.0);
Product prod3 = new Product("SKU-202", "Keyboard", 2499.0);
System.out.println("\nWith equals override:");
System.out.println(" prod1.equals(prod2): " + prod1.equals(prod2)); // true — same SKU
System.out.println(" prod1.equals(prod3): " + prod1.equals(prod3)); // false — different SKU
System.out.println(" prod1.equals(null) : " + prod1.equals(null)); // false — null safe
}
}Output:
Without equals override:
p1 == p2 : false
p1.equals(p2) : false
With equals override:
prod1.equals(prod2): true
prod1.equals(prod3): false
prod1.equals(null) : false
The five-step pattern inside equals() — same reference check, null check, type check with pattern matching, then field comparison — is not optional boilerplate. Skip the null check and equals(null) throws NullPointerException. Skip the reference check and it wastes time comparing an object to itself. Skip the type check and comparing a Product to a String throws ClassCastException.
hashCode() — The equals-hashCode Contract
hashCode() returns an integer used by HashMap, HashSet, and Hashtable to determine the bucket where an object is stored. The contract between equals() and hashCode() is rigid: if two objects are equal according to equals(), they must return the same hashCode(). Violating this contract breaks every hash-based collection in the Java standard library.
// File: HashCodeContractDemo.java
import java.util.*;
public class HashCodeContractDemo {
// Breaks the contract — equals overridden but hashCode is not
static class BrokenUser {
String userId;
BrokenUser(String id) { this.userId = id; }
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BrokenUser other)) return false;
return Objects.equals(this.userId, other.userId);
}
// hashCode NOT overridden — uses Object default (memory-based)
}
// Follows the contract — both overridden consistently
static class User {
private final String userId;
private final String email;
User(String userId, String email) {
this.userId = userId;
this.email = email;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof User other)) return false;
return Objects.equals(this.userId, other.userId);
}
@Override
public int hashCode() {
return Objects.hash(userId); // consistent with equals
}
@Override
public String toString() { return "User[" + userId + "]"; }
}
public static void main(String[] args) {
// Broken class — fails in HashSet
BrokenUser bu1 = new BrokenUser("USR-001");
BrokenUser bu2 = new BrokenUser("USR-001");
Set<BrokenUser> brokenSet = new HashSet<>();
brokenSet.add(bu1);
System.out.println("Broken contract:");
System.out.println(" bu1.equals(bu2) : " + bu1.equals(bu2)); // true — same logic
System.out.println(" Same hashCode? : " + (bu1.hashCode() == bu2.hashCode())); // false — broken
System.out.println(" Contains bu2? : " + brokenSet.contains(bu2)); // false — looks up wrong bucket
// Correct class — works in HashSet
User u1 = new User("USR-001", "priya@flipkart.com");
User u2 = new User("USR-001", "priya@flipkart.com");
Set<User> userSet = new HashSet<>();
userSet.add(u1);
System.out.println("\nCorrect contract:");
System.out.println(" u1.equals(u2) : " + u1.equals(u2)); // true
System.out.println(" Same hashCode? : " + (u1.hashCode() == u2.hashCode())); // true
System.out.println(" Contains u2? : " + userSet.contains(u2)); // true — correct bucket
Map<User, String> roleMap = new HashMap<>();
roleMap.put(u1, "ADMIN");
System.out.println(" Lookup with u2 : " + roleMap.get(u2)); // ADMIN — correct lookup
}
}Output:
Broken contract:
bu1.equals(bu2) : true
Same hashCode? : false
Contains bu2? : false
Correct contract:
u1.equals(u2) : true
Same hashCode? : true
Contains bu2? : true
Lookup with u2 : ADMIN
The broken BrokenUser class passes the equality check but silently fails in every HashSet and HashMap. contains(bu2) returns false even though an equal object is in the set — because the hash code sends the lookup to a different bucket where it never finds anything. This is one of the most common bugs in fresher pull requests.
equals() vs == — Comparison Table
| Aspect | == operator | equals() method |
|---|---|---|
| Type | Operator | Method from Object |
| For primitives | Compares values directly | Not applicable — primitives have no methods |
| For objects | Compares memory addresses (references) | Compares logical equality — depends on override |
| Default behaviour for objects | Reference equality | Reference equality (same as == if not overridden) |
| Can be overridden | No — operator behaviour is fixed | Yes — every class can define its own equality |
| Null safe | null == null is true | obj.equals(null) — throws if obj is null |
Used by HashSet / HashMap | Never | Always — alongside hashCode() |
| String comparison | Compares references — usually wrong | Compares character content — correct |
| Best practice | Use for reference identity or primitives | Use for domain equality between objects |
getClass() — Runtime Type Information
getClass() returns the runtime Class object representing the actual type of the object. It cannot be overridden — it is a final method. The returned Class object is the gateway to reflection: you can inspect methods, fields, annotations, and interfaces without knowing the type at compile time.
// File: GetClassDemo.java
import java.util.ArrayList;
import java.util.List;
public class GetClassDemo {
static class Animal {}
static class Dog extends Animal {}
public static void main(String[] args) {
Animal animal = new Dog(); // reference: Animal, actual type: Dog
Dog dog = new Dog();
String text = "DevStackFlow";
List<String> list = new ArrayList<>();
// getClass() always returns the actual runtime type
System.out.println(animal.getClass().getName()); // full class name
System.out.println(animal.getClass().getSimpleName()); // short name
System.out.println(dog.getClass().getSimpleName());
System.out.println(text.getClass().getSimpleName());
System.out.println(list.getClass().getSimpleName());
System.out.println();
// getClass() vs instanceof for type checking
System.out.println("animal instanceof Animal : " + (animal instanceof Animal)); // true
System.out.println("animal instanceof Dog : " + (animal instanceof Dog)); // true
System.out.println("getClass == Animal.class : " + (animal.getClass() == Animal.class)); // false
System.out.println("getClass == Dog.class : " + (animal.getClass() == Dog.class)); // true
System.out.println();
// Practical use in a generic logger
logType(animal);
logType(text);
logType(list);
}
public static void logType(Object obj) {
System.out.println("[LOG] Processing object of type: "
+ obj.getClass().getSimpleName());
}
}Output:
GetClassDemo$Dog
Dog
Dog
String
ArrayList
animal instanceof Animal : true
animal instanceof Dog : true
getClass == Animal.class : false
getClass == Dog.class : true
[LOG] Processing object of type: Dog
[LOG] Processing object of type: String
[LOG] Processing object of type: ArrayList
instanceof traverses the inheritance chain — a Dog is also an Animal. getClass() returns only the exact runtime type. In equals() implementations where two objects must be exactly the same class to be considered equal, getClass() is the correct check. For general type compatibility, instanceof is preferred.
clone() — Shallow Copy
clone() from Object creates a new object with the same field values. The class must implement the Cloneable marker interface, otherwise clone() throws CloneNotSupportedException. The default implementation performs a shallow copy — primitive fields are copied by value, reference fields are copied by reference (pointing to the same objects).
// File: CloneDemo.java
import java.util.ArrayList;
import java.util.List;
public class CloneDemo {
static class ShoppingCart implements Cloneable {
private final String customerId;
private double discount;
private final List<String> items; // mutable reference field
ShoppingCart(String customerId) {
this.customerId = customerId;
this.discount = 0.0;
this.items = new ArrayList<>();
}
public void addItem(String item) { items.add(item); }
public void setDiscount(double d) { this.discount = d; }
public List<String> getItems() { return items; }
public double getDiscount() { return discount; }
// Shallow clone — items list is SHARED between original and clone
@Override
public ShoppingCart clone() {
try {
return (ShoppingCart) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() {
return "Cart[" + customerId + "] discount=" + discount + " items=" + items;
}
}
public static void main(String[] args) {
ShoppingCart original = new ShoppingCart("CUST-101");
original.addItem("Laptop");
original.addItem("Mouse");
original.setDiscount(10.0);
ShoppingCart cloned = original.clone();
System.out.println("Before mutation:");
System.out.println(" Original: " + original);
System.out.println(" Clone : " + cloned);
// Mutating the primitive field — affects only the clone
cloned.setDiscount(20.0);
// Mutating the list — affects BOTH original and clone (shallow copy)
cloned.addItem("Keyboard");
System.out.println("\nAfter mutation:");
System.out.println(" Original: " + original); // list changed too
System.out.println(" Clone : " + cloned);
System.out.println("\nSame list object? "
+ (original.getItems() == cloned.getItems())); // true — shared reference
}
}Output:
Before mutation:
Original: Cart[CUST-101] discount=10.0 items=[Laptop, Mouse]
Clone : Cart[CUST-101] discount=10.0 items=[Laptop, Mouse]
After mutation:
Original: Cart[CUST-101] discount=10.0 items=[Laptop, Mouse, Keyboard]
Clone : Cart[CUST-101] discount=20.0 items=[Laptop, Mouse, Keyboard]
Same list object? true
The discount field is a primitive-backed double — changing it on the clone has no effect on the original. The items list is a reference — both objects point to the same ArrayList, so adding "Keyboard" to the clone's list modifies the shared list that both see. Deep cloning requires manually copying each mutable reference field.
wait(), notify(), notifyAll() — Thread Coordination
wait(), notify(), and notifyAll() are declared on Object — not on Thread — because Java's locking mechanism is per-object. Any object can serve as a monitor. A thread that calls wait() on an object releases that object's lock and sleeps. Another thread calling notify() on the same object wakes one waiting thread.
All three methods must be called from within a synchronized block on the same object. Calling them outside synchronized throws IllegalMonitorStateException.
// File: WaitNotifyDemo.java
public class WaitNotifyDemo {
static class OrderQueue {
private String pendingOrder = null;
private final Object lock = new Object();
public void placeOrder(String orderId) throws InterruptedException {
synchronized (lock) {
while (pendingOrder != null) {
System.out.println("[PRODUCER] Queue full — waiting...");
lock.wait(); // releases lock and waits
}
pendingOrder = orderId;
System.out.println("[PRODUCER] Order placed: " + orderId);
lock.notify(); // wakes the consumer thread
}
}
public String processOrder() throws InterruptedException {
synchronized (lock) {
while (pendingOrder == null) {
System.out.println("[CONSUMER] No order — waiting...");
lock.wait();
}
String order = pendingOrder;
pendingOrder = null;
System.out.println("[CONSUMER] Order processed: " + order);
lock.notify(); // wakes the producer if it was waiting
return order;
}
}
}
public static void main(String[] args) throws InterruptedException {
OrderQueue queue = new OrderQueue();
Thread producer = new Thread(() -> {
try {
queue.placeOrder("ORD-001");
Thread.sleep(100);
queue.placeOrder("ORD-002");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumer = new Thread(() -> {
try {
Thread.sleep(50);
queue.processOrder();
Thread.sleep(100);
queue.processOrder();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
producer.join();
consumer.join();
}
}Output:
[PRODUCER] Order placed: ORD-001
[CONSUMER] Order processed: ORD-001
[PRODUCER] Order placed: ORD-002
[CONSUMER] Order processed: ORD-002
These low-level methods are the foundation of Java's built-in thread coordination. In production code, ExecutorService, BlockingQueue, CompletableFuture, and other high-level concurrency utilities handle this more safely. But the underlying mechanism is always wait() and notify() on an object monitor.
Basic Example
public class Main {
static class Student {
private final int id;
private final String name;
Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student[" + id + ", " + name + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Student other)) return false;
return id == other.id;
}
@Override
public int hashCode() {
return Integer.hashCode(id);
}
}
public static void main(String[] args) {
Student s1 = new Student(101, "Meera");
Student s2 = new Student(101, "Meera");
System.out.println(s1);
System.out.println("s1 == s2 : " + (s1 == s2));
System.out.println("s1.equals(s2) : " + s1.equals(s2));
System.out.println("Same hashCode : " + (s1.hashCode() == s2.hashCode()));
}
}Output
Student[101, Meera] s1 == s2 : false s1.equals(s2) : true Same hashCode : true
- ›Every class implicitly extends
Object, soStudentinheritstoString(),equals(), andhashCode()even without writing them. - ›
toString()is overridden to print meaningful text instead of the defaultStudent@hashcodeformat. - ›
equals()is overridden to compareidinstead of the default reference comparison, so two differentStudentobjects with the sameidare considered equal. - ›
hashCode()is overridden to stay consistent withequals()— two objects that areequals()must return the samehashCode(), which is whys1ands2share one even thoughs1 == s2isfalse.
Best Practices
Override toString() in every domain class. Log files, error messages, and System.out.println calls all use toString() implicitly. A class without a meaningful toString() produces ClassName@hexcode in every log entry — useless for debugging. Make it a habit: any class that represents a domain concept gets a toString().
Always override hashCode() when you override equals(). The IDE can generate both. The risk of overriding one without the other is that HashSet.contains() and HashMap.get() silently return wrong results. Most static analysis tools will warn about an equals() without a corresponding hashCode() — listen to that warning.
Use Objects.equals() and Objects.hash() in your implementations. Objects.equals(a, b) handles null safely — no need to null-check each field before comparing. Objects.hash(field1, field2) computes a consistent combined hash without writing the prime-multiplication boilerplate manually.
Never call wait() or notify() in a loop without a condition check. Spurious wakeups — where a thread wakes up from wait() without notify() being called — are a documented JVM behaviour. Always wrap wait() in a while loop that re-checks the condition, not an if. The pattern while (!condition) { lock.wait(); } prevents acting on a spurious wakeup.
Common Mistakes
Mistake 1 — Overriding equals() Without hashCode()
public class Session {
private String sessionId;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Session other)) return false;
return Objects.equals(sessionId, other.sessionId);
}
// hashCode() not overridden — uses Object.hashCode() (memory-based)
// Two equal Session objects return different hash codes
// HashSet<Session> will contain duplicates — silently
}This compiles without error. The bug only surfaces at runtime when sessions appear duplicated in a HashSet or lookups in a HashMap return null for objects that are clearly present.
Mistake 2 — Using == to Compare Objects
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false — different objects in memory
System.out.println(a.equals(b)); // true — same character contentUsing == on Strings — and on any object — is almost always wrong unless you specifically need reference identity. String literals benefit from the String pool, so "hello" == "hello" sometimes returns true, which misleads beginners into thinking == works for content comparison. It does not. Use equals().
Mistake 3 — Calling wait() Without synchronized
public class OrderProcessor {
private final Object lock = new Object();
public void waitForOrder() throws InterruptedException {
// Throws IllegalMonitorStateException — not inside synchronized
lock.wait();
}
}Runtime exception: java.lang.IllegalMonitorStateException
wait(), notify(), and notifyAll() must be called inside a synchronized block on the same object they are called on. The thread must hold the object's monitor before it can release it via wait(). Calling outside synchronized always throws IllegalMonitorStateException.
Mistake 4 — Modifying Fields Used in equals() and hashCode() After Insertion
Product p = new Product("SKU-001", "Headphones", 1999.0);
Set<Product> set = new HashSet<>();
set.add(p); // stored in bucket based on hash of "SKU-001"
p.setSku("SKU-002"); // mutates the field used in hashCode()
// The object is now in the wrong bucket — set.contains(p) returns false
System.out.println(set.contains(p)); // false — even though p is in the setOnce an object is stored in a HashSet or as a key in a HashMap, mutating the fields that contribute to hashCode() corrupts the data structure. The object ends up in a bucket its current hash does not match. Either use immutable objects as keys, or use fields that never change — like IDs — in your equals() and hashCode().
Interview Questions
Q1. What is the Object class in Java and why does every class extend it?
java.lang.Object is the root of Java's class hierarchy. Every class implicitly extends it when no explicit parent is declared. This guarantees that every object in Java shares a common set of methods — toString(), equals(), hashCode(), getClass(), clone(), wait(), notify(), and notifyAll(). This single-root design makes polymorphic collections, reflection, and thread coordination possible without requiring explicit declarations in every class.
Q2. What is the contract between equals() and hashCode()?
If two objects are equal according to equals(), they must return the same value from hashCode(). The reverse is not required — two objects with the same hash code are not necessarily equal. Violating this contract breaks HashSet, HashMap, and Hashtable. An object stored in a HashSet with one hash code becomes unfindable if its fields change after insertion, because contains() looks in the bucket corresponding to the new hash.
Q3. What does the default equals() implementation in Object do?
The default equals() in Object compares references — it returns true only when both references point to the exact same object in memory. It is identical to the == operator for objects. For domain classes where equality means "same data", this default is almost always wrong and must be overridden to compare meaningful fields.
Q4. Why are wait(), notify(), and notifyAll() methods on Object and not on Thread?
Java's synchronisation mechanism is per-object, not per-thread. Any object can serve as a monitor — the lock is on the object, and threads compete for it. A thread waits on an object's monitor and releases that object's lock. If these methods were on Thread, there would be no way to associate the wait condition with a specific shared resource. Placing them on Object means any object can coordinate threads that share it.
Q5. What is the difference between == and equals() in Java?
== on objects compares memory addresses — it returns true only if both references point to the exact same object. equals() compares logical equality — the result depends on the override. For String, equals() compares character content. For a custom Product class, equals() might compare only the SKU. The key rule: always use equals() for content comparison between objects, and == only for reference identity or primitive comparison.
Q6. What is a shallow copy versus a deep copy in the context of Object.clone()?
Object.clone() produces a shallow copy — a new object with field values copied from the original. For primitive fields and immutable references like String, this works correctly. For mutable reference fields like List or Map, both the original and the clone point to the same underlying object, so mutations through either reference affect both. A deep copy requires explicitly creating new copies of every mutable referenced object, usually through a copy constructor or a custom clone() implementation that recursively copies all mutable fields.
FAQs
Can you instantiate the Object class directly?
Yes — new Object() is valid Java. Object is a concrete class, not abstract. However, a plain Object instance has almost no useful behaviour — its toString() returns a memory address, and equals() checks reference identity. In practice, creating a plain Object is used only as a lock object in concurrency code: private final Object lock = new Object().
What happens if you call toString() on a null reference?
Calling any method on null throws NullPointerException. However, String.valueOf(null) returns the string "null", and string concatenation "" + null also produces "null" without throwing. When logging objects that might be null, use String.valueOf(object) or Objects.toString(object, "default") for safe string conversion.
Is finalize() still used in modern Java?
No. finalize() was deprecated in Java 9 and marked for removal. It was called by the garbage collector before reclaiming an object, but the timing was unpredictable and the implementation was complex and fragile. Use try-with-resources and AutoCloseable for deterministic resource cleanup. java.lang.ref.Cleaner is the modern replacement for post-GC cleanup in rare cases where it is genuinely needed.
Why does toString() in Object return a hex number?
The default format is ClassName@hashCode where the hash code is displayed in hexadecimal. The hash code from the default Object.hashCode() is derived from the object's memory address (though not guaranteed to be the actual address after JVM optimisations). It is unique enough to distinguish objects but carries no domain meaning — which is exactly why every domain class should override it.
What does Objects.requireNonNull() do and how does it relate to Object class?
Objects.requireNonNull(value) is a utility method from java.util.Objects — not from java.lang.Object. It throws NullPointerException immediately if the value is null, with a clear message. Using it in constructors catches null arguments at creation time rather than when the null reference is finally dereferenced later in the code, which makes the exception appear at the root cause rather than at a symptom.
Summary
java.lang.Object is the invisible foundation every Java class builds on. Its eleven methods are available on every object you create — and three of them, equals(), hashCode(), and toString(), need to be overridden in almost every domain class you write.
The most consequential rule is the equals()-hashCode() contract. Overriding one without the other produces code that compiles cleanly and fails silently in every hash-based collection. The pattern is always the same: override both, base them on the same fields, and use Objects.equals() and Objects.hash() to handle null safely.
getClass() and instanceof answer related questions — getClass() for exact type identity, instanceof for type compatibility. clone() produces a shallow copy that surprises developers who expect a deep one. wait(), notify(), and notifyAll() power Java's built-in thread coordination and require a synchronized block to use correctly.
For interviews, know the equals()-hashCode() contract cold, be ready to write a correct equals() implementation from memory, explain why wait() lives on Object rather than Thread, and describe the difference between == and equals() with a concrete example.
What to Read Next
Learn how to control how an object prints as text.
Practice Programs
Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.
Shows the default toString(), equals(), and getClass() behavior every class inherits from Object, before any overriding.
Overrides toString() to return a custom, readable string instead of Java's default format.
Overrides equals() to compare two objects by their field values instead of reference identity.
Overrides hashCode() so it stays consistent with an equals() override.