Back to Inner Classes
Q1Question 1 of 2

What is an inner class and what are the four types?

Key Concepts
  • Static nested class: declared static, no reference to outer instance – like a regular class inside another for namespacing
  • Inner class (non-static): holds an implicit reference to the outer instance – can access outer's private fields
  • Local class: defined inside a method – can capture effectively final local variables
  • Anonymous class: unnamed class defined inline – commonly used for one-off implementations (before lambdas)
  • Since Java 8, lambdas replace most anonymous class use cases for functional interfaces

Definition

Java has four types of nested classes: static nested (no outer reference), inner (has outer reference), local (inside a method), and anonymous (unnamed, inline). Lambdas largely replace anonymous classes.

Four types:

  • Static nested class: static class Builder { } inside Account. Does not need an outer Account instance. Used for builders, helpers, data structures.
  • Inner class (non-static): class Iterator { } inside Account. Holds an implicit reference to the enclosing Account. Can access all outer fields including private.
  • Local class: Defined inside a method body. Captures effectively final variables from the method.
  • Anonymous class: No name, defined inline: new Comparator() { ... }. One-off implementation.

A static nested class is like an ATM installed in the bank lobby – it is physically inside but operates independently. An inner class is like the bank manager's personal assistant – tied to one specific manager and can access their private schedule. A local class is like a temporary form filled out during one transaction. An anonymous class is like a sticky note with a quick instruction – used once and discarded.

Understanding the Concepts

Static nested class

A static nested class is declared with the static keyword inside another class. It does not hold any reference to an outer class instance, so it behaves like a top-level class that is namespaced inside the outer class. The most common use is the Builder pattern.

JAVA
package com.bank.core;

import java.math.BigDecimal;

public class Account {
    private final String id;
    private final BigDecimal balance;

    private Account(String id, BigDecimal balance) {
        this.id = id;
        this.balance = balance;
    }

    // Static nested: no reference to any Account instance
    public static class Builder {
        private String id;
        private BigDecimal balance = BigDecimal.ZERO;

        public Builder id(String id) { this.id = id; return this; }
        public Builder balance(BigDecimal b) { this.balance = b; return this; }
        public Account build() { return new Account(id, balance); }
    }
}

// Create without any Account instance
Account a = new Account.Builder()
        .id("SAV2001")
        .balance(new BigDecimal("500000"))
        .build();

The Builder class is static, so you create it with new Account.Builder() – no Account object is needed.

Inner class (non-static)

An inner class (non-static nested class) holds an implicit reference to the enclosing outer instance. It can access all of the outer instance's fields, including private ones.

JAVA
package com.bank.core;

import java.util.ArrayList;
import java.util.List;

public class Account {
    private List<String> transactions = new ArrayList<>();

    // Inner class: has access to Account's private fields
    public class TransactionIterator {
        private int index = 0;
        public boolean hasNext() { return index < transactions.size(); }
        public String next() { return transactions.get(index++); }
    }

    public TransactionIterator iterator() {
        return new TransactionIterator();  // implicitly tied to this Account
    }
}

// Usage: needs an Account instance
Account a = new Account();
Account.TransactionIterator it = a.iterator();

// Or using the outer.new syntax:
Account.TransactionIterator it2 = a.new TransactionIterator();

The inner class has an implicit Account.this reference. You can access it explicitly: Account.this.transactions.

Local class (inside a method)

A local class is defined inside a method body. It can access the method's local variables, but only if they are effectively final (never reassigned after initialization).

JAVA
package com.bank.report;

import java.math.BigDecimal;
import java.util.List;

public class ReportGenerator {
    public void generateSummary(List<BigDecimal> balances) {
        final String bankName = "National Bank of India";

        // Local class defined inside a method
        class SummaryFormatter {
            String format(BigDecimal total) {
                return bankName + " – Total: " + total;  // captures bankName
            }
        }

        BigDecimal total = balances.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
        SummaryFormatter formatter = new SummaryFormatter();
        System.out.println(formatter.format(total));
    }
}

Local classes are rare in modern Java because lambdas handle most of these use cases more concisely.

Anonymous class vs lambda

An anonymous class is an unnamed class defined inline. Before Java 8, this was the standard way to implement callbacks and event handlers. Since Java 8, lambdas replace most anonymous class use cases for functional interfaces.

JAVA
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

// Anonymous class (pre-Java 8)
Collections.sort(accounts, new Comparator<Account>() {
    @Override
    public int compare(Account a, Account b) {
        return a.getBalance().compareTo(b.getBalance());
    }
});

// Lambda (Java 8+): replaces anonymous class for functional interfaces
accounts.sort((a, b) -> a.getBalance().compareTo(b.getBalance()));

// Or method reference – the cleanest form
accounts.sort(Comparator.comparing(Account::getBalance));

Anonymous classes are still needed when you need to implement an interface with multiple methods or extend a class.

When to use which?

| Type | Use when |
|---|---|
| Static nested | Logically grouped with outer, no outer reference needed (Builder, Entry, Node) |
| Inner class | Needs access to outer instance's state (Iterator, Handler) |
| Local class | Need a named class inside one method (rare – lambdas are usually better) |
| Anonymous class | One-off implementation of interface/abstract class (replaced by lambdas for SAM) |
| Lambda | Functional interface implementation (prefer this since Java 8) |

What are common mistakes?

The most dangerous mistake is creating a non-static inner class in a context where it accidentally prevents the outer object from being garbage collected.

JAVA
package com.bank.service;

public class NotificationService {
    private byte[] largeBuffer = new byte[10_000_000];  // 10 MB

    // WRONG: non-static inner class holds a reference to NotificationService
    public class Handler implements Runnable {
        @Override
        public void run() {
            System.out.println("Handling notification");
        }
    }

    // The Handler holds a hidden reference to the NotificationService.
    // If Handler is stored in a long-lived collection, the NotificationService
    // and its 10 MB buffer cannot be garbage collected.
}

// FIX: make it static
public static class Handler implements Runnable {
    @Override
    public void run() {
        System.out.println("Handling notification");
    }
}

What should a senior engineer know?

  • Inner classes hold a hidden reference to the outer instance. This can prevent GC of the outer object and cause memory leaks. Android development famously had memory leaks from non-static inner classes in Activities.
  • Static nested is preferred unless you genuinely need the outer reference.
  • Map.Entry is a static nested interface inside Map. HashMap.Node is a static nested class.
  • Records can be nested: record AccountSummary(String id, BigDecimal balance) {} inside a class. Nested records are implicitly static.
  • Lambdas capture the enclosing this if they reference instance members, similar to inner classes. This can also cause retention issues.
  • Serialization of inner classes is discouraged because the synthetic fields (like the outer reference) create hidden coupling.

Visual Overview

DIAGRAM
  Four types of nested classes:

  1. static class Builder { }      ← no outer reference, like a top-level class
  2. class Iterator { }            ← holds outer reference, accesses private fields
  3. class Local { } (in method)   ← captures effectively final locals
  4. new Interface() { }           ← anonymous, replaced by lambdas

  Memory consideration:
    Inner class → holds reference to outer → can prevent outer's GC
    Static nested → no outer reference → safe

Key Takeaway

Java has four types of nested classes. Static nested classes are grouped inside another class for namespacing but operate independently. Inner classes hold a reference to the enclosing instance and can access its private fields. Local classes are defined inside a method. Anonymous classes are unnamed inline implementations, largely replaced by lambdas since Java 8.

Prefer static nested classes unless you genuinely need the outer instance reference, because inner classes hold a hidden reference that can prevent garbage collection.

One sentence to remember: static nested has no outer reference, inner class has one – prefer static nested, and prefer lambdas over anonymous classes.

---

Share
Use ← → arrow keys to navigate