Definition
Four types:
- Static nested class:
static class Builder { }insideAccount. Does not need an outerAccountinstance. Used for builders, helpers, data structures. - Inner class (non-static):
class Iterator { }insideAccount. 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.
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.
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).
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.
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.
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.Nodeis 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
thisif 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
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.
---