The Object Class and Its Core Methods
Interview Question: "Every class in Java inherits from
java.lang.Object. What are its 11 core methods? What are the strict mathematical contracts forequals()andhashCode(), why ishashCode()NOT guaranteed to be a unique memory address, and how do you choose betweengetClass()andinstanceofinequals()?"
The Quick Answer
java.lang.Object is the root of the Java class hierarchy, exposing 11 methods covering equality (equals, hashCode), representation (toString), lifecycle (clone, finalize, getClass), and thread concurrency (wait with three overloads, notify, notifyAll). Overriding equals() requires adhering to five formal properties (reflexive, symmetric, transitive, consistent, non-null) and strictly maintaining the hashCode() contract where equal objects MUST yield identical hash codes. While instanceof allows symmetric equality across subclasses that add behavior without state, getClass() is strictly required when subclasses introduce new state fields to prevent breaking symmetry and transitivity.
The 11 Methods of java.lang.Object
In the Java type hierarchy, java.lang.Object sits at the root. Every class, including arrays, inherits its 11 methods directly:
| Method Signature | Overridable? | Purpose |
|---|---|---|
public boolean equals(Object obj) | Yes | Logical equality comparison (default implementation uses == reference identity). |
public native int hashCode() | Yes | Computes a 32-bit integer hash code for hashing data structures like HashMap. |
public String toString() | Yes | Human-readable string representation (ClassName@HexHashCode). |
protected native Object clone() | Yes | Creates a field-by-field shallow copy (requires implementing Cloneable). |
public final native Class<?> getClass() | No (final) | Returns the runtime Class descriptor for reflection and type checks. |
public final native void notify() | No (final) | Wakes up a single thread waiting on this object's monitor lock. |
public final native void notifyAll() | No (final) | Wakes up all threads waiting on this object's monitor lock. |
public final void wait() | No (final) | Releases monitor lock and suspends calling thread indefinitely. |
public final native void wait(long timeoutMillis) | No (final) | Releases monitor lock with timeout in milliseconds. |
public final void wait(long timeout, int nanos) | No (final) | Releases monitor lock with high-precision timeout in nanoseconds. |
protected void finalize() | Yes | Deprecated in Java 9, condemned in Java 18. Destructor hook prior to GC. |
The 5 Strict Mathematical Contracts of equals()
When overriding equals(), Section 17.7 of the Java Language Specification mandates five formal properties:
- Reflexive: For any non-null reference
x,x.equals(x)must returntrue. - Symmetric: For any non-null references
xandy,x.equals(y)must returntrueif and only ify.equals(x)returnstrue. - Transitive: For any non-null references
x,y, andz, ifx.equals(y)returnstrueandy.equals(z)returnstrue, thenx.equals(z)must returntrue. - Consistent: Multiple invocations of
x.equals(y)must consistently return the same boolean result, provided no fields used in comparisons are modified. - Non-nullity: For any non-null reference
x,x.equals(null)must returnfalse(and never throwNullPointerException).
The equals() & hashCode() Contract & The Memory Address Myth
Common Misconception Debunked:
hashCode()is NOT a guaranteed unique memory address!
1. Why hashCode() is NOT a Memory Address
- Pigeonhole Principle: A modern 64-bit JVM operates over an address space of up to
2^64bytes (16 exabytes). In contrast, anintis a signed 32-bit integer representing only2^32(~4.29 billion) distinct values. Hash collisions are mathematically unavoidable. - Dynamic Heap Relocation: Modern garbage collectors (G1, ZGC, Shenandoah) dynamically compact the heap by physically relocating live objects to new memory addresses. An object's physical address changes over time, but its
hashCode()must remain invariant. - Mark Word Storage: HotSpot generates the default identity hash code using a pseudo-random number generator or thread state on first call and lazily caches it inside the Mark Word of the object's 64-bit header.
2. The Golden Contract
- Rule 1 (Mandatory): If
x.equals(y) == true, thenx.hashCode() == y.hashCode()must always hold. - Rule 2: If
x.hashCode() == y.hashCode(),x.equals(y)does NOT need to betrue(hash collision). - The Consequence: If you override
equals()but fail to overridehashCode(), your objects will vanish insideHashMapandHashSet. Two logically identical objects will hash to different buckets, preventingmap.get(key)from ever locating the stored value.
Comparison: getClass() vs. instanceof in equals()
A staff-level interview question asks how to choose between getClass() and instanceof when writing equals():
| Dimension | getClass() | instanceof |
|---|---|---|
| Type Equality Check | Exact concrete type match (this.getClass() == obj.getClass()). | Type compatibility match (obj instanceof Superclass). |
| Subclass State Extensions | Immune to bugs. Point and ColorPoint are never equal, preserving symmetry and transitivity. | Fatal trap. If a subclass introduces new fields (e.g. color), satisfying symmetry and transitivity is mathematically impossible. |
| Liskov Substitution (LSP) | Strictly violates LSP if a subclass adds only behavior/methods without new state fields. | Honors LSP for behavioral subtyping across inheritance hierarchies. |
| Null Safety | Requires explicit if (obj == null) check; otherwise throws NullPointerException. | Safe by design: null instanceof Class evaluates cleanly to false. |
| Recommended Usage | Domain entities where identity depends on exact class and subclass state. | Value objects or class hierarchies where subtyping never introduces state. |
Option A: Strict Type Equality (getClass())
if (obj == null || getClass() != obj.getClass()) return false;
- Behavior: Enforces strict type equality. Two objects are only equal if they belong to the exact same concrete class.
- Advantage: Completely immune to the subclass state-extension trap. If
ColorPoint extends Point, aPointwill never equal aColorPoint, maintaining symmetry and transitivity. - Trade-off: Violates the Liskov Substitution Principle if a subclass adds only behavior (no state) and intends to be interoperable with its parent.
Option B: Type Compatibility (instanceof)
if (!(obj instanceof Point)) return false;
- Behavior: Allows subclasses to be equal to superclass instances.
- Advantage: Honors LSP for behavioral subtyping.
- Fatal Trap: If the subclass introduces a new field (e.g.,
color), satisfying both symmetry and transitivity simultaneously is mathematically impossible (Joshua Bloch, Effective Java, Item 10). IfPointignores color,p.equals(cp)istrue, butcp.equals(p)checks color and returnsfalse(violating symmetry). Ifcp.equals(p)ignores color when comparing with aPoint, transitivity breaks when comparing two differentColorPointinstances with identicalPointcoordinates.
The Interview Answer (60-90 seconds)
"The
java.lang.Objectclass provides 11 foundational methods:equals,hashCode, andtoStringfor identity and representation;clonefor shallow copying;getClassfor reflection;finalizewhich is deprecated; and five concurrency primitives (waitwith three overloads,notify, andnotifyAll) that coordinate threads around an object's internal monitor lock.The
equals()method must obey five mathematical contracts: reflexivity, symmetry, transitivity, consistency, and non-nullity. Its relationship withhashCode()is critical: if two objects are equal according toequals(), theirhashCode()outputs must be identical. Violating this breaks hash-based collections likeHashMap.It is a common myth that
hashCode()returns the object's physical memory address. Because modern garbage collectors move objects during compaction and 64-bit addresses cannot map uniquely into a 32-bit integer, HotSpot caches a generated hash in the object's Mark Word header.Finally, when writing
equals(), usinggetClass()enforces strict type equality, preventing violations of symmetry and transitivity when subclasses add state, whereasinstanceofis used only when subtyping does not introduce new fields."
Code Demonstration: Bulletproof equals(), hashCode(), and HashMap Contract
The following Java program demonstrates the canonical implementation of equals() and hashCode() and shows what happens when the contract is broken in a HashMap.
- ObjectMethodsDemo.java
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ObjectMethodsDemo {
// Correct implementation honoring equals and hashCode contracts
static final class User {
private final long id;
private final String email;
public User(long id, String email) {
this.id = id;
this.email = email;
}
@Override
public boolean equals(Object o) {
// 1. Reflexivity check
if (this == o) return true;
// 2. Non-nullity & Strict class equality check
if (o == null || getClass() != o.getClass()) return false;
// 3. State comparison
User user = (User) o;
return id == user.id && Objects.equals(email, user.email);
}
@Override
public int hashCode() {
// Consistent with equals()
return Objects.hash(id, email);
}
@Override
public String toString() {
return "User{id=" + id + ", email='" + email + "'}";
}
}
// Broken class violating the contract (overrides equals but NOT hashCode)
static final class BrokenUser {
private final long id;
public BrokenUser(long id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BrokenUser that = (BrokenUser) o;
return id == that.id;
}
// hashCode() intentionally omitted!
}
public static void main(String[] args) {
System.out.println("--- 1. Testing Valid User in HashMap ---");
Map<User, String> userMap = new HashMap<>();
User u1 = new User(101, "dev@example.com");
User u2 = new User(101, "dev@example.com");
userMap.put(u1, "Senior Engineer");
System.out.println("u1.equals(u2): " + u1.equals(u2));
System.out.println("u1.hashCode() == u2.hashCode(): " + (u1.hashCode() == u2.hashCode()));
System.out.println("Map lookup with u2: " + userMap.get(u2)); // Returns "Senior Engineer"!
System.out.println("\n--- 2. Testing BrokenUser in HashMap ---");
Map<BrokenUser, String> brokenMap = new HashMap<>();
BrokenUser b1 = new BrokenUser(202);
BrokenUser b2 = new BrokenUser(202);
brokenMap.put(b1, "Database Admin");
System.out.println("b1.equals(b2): " + b1.equals(b2)); // true
System.out.println("b1.hashCode() == b2.hashCode(): " + (b1.hashCode() == b2.hashCode())); // false!
System.out.println("Map lookup with b2: " + brokenMap.get(b2)); // Returns NULL! Object is lost!
}
}