Nested and Anonymous Classes
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A nested class is a class declared inside another class. Java has four kinds and they exist to keep a helper type next to the one place that uses it. This page follows the Java Tutorials "Nested Classes" lesson and Anonymous Classes.
| Kind | Enclosing instance? | Typical use |
|---|---|---|
|
no |
a helper type namespaced under the outer class |
inner (non- |
yes — |
needs the outer object’s state: iterators, live views |
local |
yes |
a named helper needed inside a single method |
anonymous |
yes |
a one-off implementation of an interface or subclass |
static Nested Classes
A static nested class is really a top-level class that happens to live inside another for namespacing.
It holds no reference to an instance of the outer class and can reach only the outer class’s static
members. Use it for a helper conceptually owned by the outer type — a builder, a key, a linked-list
node.
public class LinkedStack<E> {
// static: a Node never needs to see a particular LinkedStack instance
private static final class Node<E> {
final E value;
Node<E> next;
Node(E value, Node<E> next) {
this.value = value;
this.next = next;
}
}
private Node<E> head;
public void push(E value) {
head = new Node<>(value, head);
}
public E pop() {
if (head == null) {
throw new java.util.NoSuchElementException("empty");
}
E value = head.value;
head = head.next;
return value;
}
}
A static nested class is referred to as Outer.Nested and, where visible, instantiated with no outer
object: new LinkedStack.Node<>(…). A generic nested class declares its own type parameters, as
Node<E> does above.
Inner (Non-static) Classes
Drop static and the class becomes an inner class. Every instance is bound to an instance of the
enclosing class and can use its members directly, including private ones. The enclosing instance is
named explicitly as Outer.this.
import java.util.ArrayList;
import java.util.List;
public class Rolodex {
private final List<String> names = new ArrayList<>();
public void add(String name) {
names.add(name);
}
// inner class: each Cursor belongs to exactly one Rolodex
public class Cursor {
private int index = 0;
public boolean hasNext() {
return index < names.size(); // names is Rolodex.this.names
}
public String next() {
return names.get(index++);
}
public Rolodex owner() {
return Rolodex.this; // the explicit enclosing instance
}
}
public Cursor cursor() {
return new Cursor();
}
}
From inside the outer class you write new Cursor(); from outside you qualify it with the outer object
using the outer.new Inner() form:
var rolodex = new Rolodex();
rolodex.add("Ada");
rolodex.add("Bo");
Rolodex.Cursor c = rolodex.new Cursor(); // outer.new Inner()
while (c.hasNext()) {
System.out.println(c.next());
}
Since Java 16 an inner class may also declare its own static members; before that only compile-time
constant static final fields were allowed.
The memory-leak caveat
Because an inner-class instance holds a hidden reference to Outer.this, it keeps the entire
enclosing instance reachable for as long as the inner instance lives. Hand an inner-class object to
something longer-lived than the outer object — a static registry, an event bus, a cache, a background
executor — and the outer object, plus everything it holds, can no longer be garbage-collected. When the
nested type does not truly need the enclosing instance, make it static and pass what it needs through
the constructor.
Local Classes
A local class is a named class declared inside a method, constructor, or initializer block. It is visible only within that block and may capture local variables that are effectively final — assigned once and never reassigned. See Local Classes.
import java.util.ArrayList;
import java.util.List;
static List<String> render(List<String> lines, String prefix) {
class Row { // local class: visible only in render(...)
final String key;
final String value;
Row(String raw) {
int eq = raw.indexOf('=');
this.key = prefix + raw.substring(0, eq).trim(); // captures prefix (effectively final)
this.value = raw.substring(eq + 1).trim();
}
String line() {
return key + " -> " + value;
}
}
var out = new ArrayList<String>();
for (var line : lines) {
if (line.contains("=")) {
out.add(new Row(line).line());
}
}
return out;
}
A local class can see the enclosing method’s parameters and locals (if effectively final) and the enclosing instance’s members. Reach for one only when the helper needs a name and more than a single expression; otherwise a lambda is lighter.
Anonymous Classes
An anonymous class fuses the declaration and one instantiation into a single expression: it implements an interface or extends a class on the spot, with no name.
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicInteger;
// implements an interface inline
Comparator<String> byLengthThenText = new Comparator<>() {
@Override
public int compare(String a, String b) {
int byLen = Integer.compare(a.length(), b.length());
return byLen != 0 ? byLen : a.compareTo(b);
}
};
// extends a class inline, adding its own state
var trackingCounter = new AtomicInteger() {
int toStringCalls = 0;
@Override
public String toString() {
toStringCalls++;
return "value=" + get() + " (toString calls: " + toStringCalls + ")";
}
};
Like a local class, an anonymous class captures effectively-final locals. Inside it, this refers to
the anonymous instance itself, not the enclosing object — reach the enclosing object as
Enclosing.this. The new Comparator<>() diamond form (Java 9+) lets the compiler infer the anonymous
class’s type argument.
Anonymous class or lambda?
If the target is a functional interface (one abstract method) and you need no extra state and no name, a lambda is shorter and cheaper — see Lambdas and Method References. Choose an anonymous class when you need something a lambda cannot provide:
-
more than one method to implement or override — for example overriding both
toStringand another method, or implementing a multi-method callback interface; -
instance fields of its own to carry state between calls;
-
to extend a class rather than implement an interface;
-
a body where
thismust mean the new object rather than the enclosing one.
// a Runnable has one method and no state: prefer the lambda
Runnable viaAnonymous = new Runnable() {
@Override
public void run() {
System.out.println("tick");
}
};
Runnable viaLambda = () -> System.out.println("tick"); // same effect, no new class scope
See Also
-
Classes and Objects — fields, constructors, and access modifiers that nested classes build on.
-
Lambdas and Method References — the lighter tool for one-method behaviour.
-
Interfaces — functional interfaces implemented by anonymous classes and lambdas alike.
-
Inheritance and Polymorphism — subclassing, which an anonymous class does inline.