Back to Object Class, equals & hashCode
Q1Question 1 of 9

What is the superclass of all Java classes?

Key Concepts
  • java.lang.Object is the root of every class hierarchy – every class extends Object implicitly
  • Object provides 11 methods: toString(), equals(), hashCode(), getClass(), clone(), finalize(), wait(), notify(), notifyAll(), plus 2 overloaded wait()
  • If you write class Account {}, it implicitly extends Object
  • You can override toString(), equals(), and hashCode() for meaningful behavior
  • getClass() returns the runtime class, useful for reflection and logging

Definition

java.lang.Object is the implicit superclass of every Java class. It provides toString(), equals(), hashCode(), getClass(), clone(), wait(), notify(), and finalize().

Object class facts:

  • Root of all classes. Every class extends Object, even if not declared explicitly.
  • 11 methods inherited by every object: toString, equals, hashCode, getClass, clone, finalize (deprecated), wait (3 overloads), notify, notifyAll.
  • Default implementations: toString returns className@hexHashCode, equals uses ==, hashCode returns a memory-based value.

Object is the standard bank form template. Every specific form (Account, Transaction) is based on this template, even if nobody explicitly says so. The template provides generic features like a form number (hashCode), a description field (toString), and an identity check (equals).

Understanding the Concepts

Object methods overview

JAVA
public class Account {
    // Implicitly: extends Object
    private String accountId;
    private BigDecimal balance;
}

Account a = new Account();

// toString: default is className@hex
System.out.println(a.toString());  // com.bank.Account@1a2b3c4d

// equals: default is == (reference comparison)
Account b = new Account();
System.out.println(a.equals(b));  // false (different objects)

// hashCode: default is memory-based
System.out.println(a.hashCode());  // some integer

// getClass: runtime class
System.out.println(a.getClass().getName());  // com.bank.Account

Overriding toString

JAVA
@Override
public String toString() {
    return "Account{id=" + accountId + ", balance=" + balance + "}";
}
// Now: System.out.println(a) → Account{id=SAV2001, balance=500000}

Which methods should you override?

| Method | Override? | Why |
|---|---|---|
| toString() | Yes | Readable logging and debugging |
| equals() | Yes, for value objects | Correct equality comparison |
| hashCode() | Yes, if equals() is overridden | HashMap/HashSet correctness |
| clone() | Rarely | Prefer copy constructor |
| finalize() | Never | Deprecated since Java 9 |

What should a senior engineer know?

  • Records auto-generate toString, equals, hashCode from all components.
  • Lombok @Data generates toString, equals, hashCode, getters, setters.
  • getClass() vs instanceof in equals: getClass() is strict (exact type match), instanceof allows subclass equality. Choose based on your hierarchy design.

What are common mistakes?

A common mistake is overriding equals() without overriding hashCode(), which breaks HashMap and HashSet behavior.

Wrong approach:

JAVA
package com.bank.model;

public class Account {
    private String accountId;

    // WRONG: overrides equals but not hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account)) return false;
        return accountId.equals(((Account) o).accountId);
    }
    // Missing hashCode override!
}
JAVA
// This breaks HashMap lookups
Map<Account, String> map = new HashMap<>();
Account a1 = new Account("SAV2001");
map.put(a1, "Rajesh");
Account a2 = new Account("SAV2001");
System.out.println(map.get(a2));  // null! Different hashCode

Correct approach:

JAVA
package com.bank.model;

import java.util.Objects;

public class Account {
    private String accountId;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account)) return false;
        return accountId.equals(((Account) o).accountId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(accountId);
    }
}

Now both a1 and a2 produce the same hash code, so HashMap lookups work correctly.

What are the edge cases?

Edge cases can cause unexpected behavior in banking applications. Here are some to watch for.

JAVA
package com.bank.edge;

public class EdgeCaseDemo {
    public static void main(String[] args) {
        // Edge case 1: null handling
        String accountId = null;
        try {
            System.out.println(accountId.length());
        } catch (NullPointerException e) {
            System.out.println("Null account ID");
        }

        // Edge case 2: empty vs null
        String emptyId = "";
        System.out.println(emptyId.isEmpty());  // true
        System.out.println(emptyId == null);     // false

        // Edge case 3: boundary values
        java.math.BigDecimal zero = java.math.BigDecimal.ZERO;
        java.math.BigDecimal negativeBalance = new java.math.BigDecimal("-0.01");
        System.out.println(zero.compareTo(negativeBalance) > 0);  // true
    }
}
TEXT
Null account ID
true
false
true

Always test with null values, empty strings, boundary values, and negative amounts in banking applications.

What are common interview follow-up questions?

Interviewers often follow up with these related questions:

  • How does this concept apply to a high-throughput banking transaction system?
  • What are the performance implications in a production environment?
  • How would you debug issues related to this concept?
  • What tools would you use to monitor this in production?
  • How does this behave differently in Java 8 vs Java 17+?

When answering follow-up questions, always relate your answer back to practical experience. For example, at National Bank of India, Rajesh debugged a production issue related to this concept by using JFR recordings and heap dumps. Providing concrete examples from banking systems shows the interviewer that you understand both the theory and the practice.

Visual Overview

DIAGRAM
  java.lang.Object
    │
    ├── toString()    → "Account@1a2b3c4d" (override for readability)
    ├── equals()      → == by default (override for value comparison)
    ├── hashCode()    → memory-based (override with equals)
    ├── getClass()    → runtime Class object
    ├── clone()       → shallow copy (prefer copy constructor)
    ├── finalize()    → deprecated, never use
    ├── wait()        → thread synchronization
    ├── notify()      → thread synchronization
    └── notifyAll()   → thread synchronization

  Every class extends Object implicitly.

Key Takeaway

java.lang.Object is the root of every Java class. It provides default implementations of toString (class@hash), equals (reference comparison), hashCode (memory-based), and other methods. Override toString for readable output, equals for value comparison, and hashCode to match equals for HashMap correctness.

One sentence to remember: every class extends Object, which provides toString, equals, hashCode, and getClass – override the first three for meaningful behavior.

---

Share
Use ← → arrow keys to navigate