Inner Classes and Nested Classes in Java
Inner Classes and Nested Classes
Nested classes were covered at the Intermediate level mainly as an organisational tool. At this Advanced level, the focus shifts to precision: exactly what each variant can and cannot access, the bytecode-level reason for those rules, and the situations where professional codebases actually reach for each one.
Static Nested Classes
A static nested class is the simplest variant. It carries no implicit reference to any enclosing instance, which means it behaves, for all practical purposes, like a regular top-level class that happens to be scoped inside another class's name. This is precisely why it can be instantiated without ever creating an object of the outer class.
class Tree {
Node root;
static class Node { // static nested class, no Tree link
int value;
Node left, right;
Node(int v) {
value = v;
}
}
void insert(int v) {
root = new Node(v); // created directly, no enclosing Tree instance required
}
}
This pattern, a small static nested class representing a building block of its outer class, like Node inside Tree, is extremely common in data-structure implementations throughout the Java standard library itself, including inside java.util.TreeMap and java.util.LinkedList.
Non-Static Inner Classes
A non-static inner class, by contrast, holds an implicit reference to the specific outer object that created it. The compiler actually generates a hidden field, conventionally something like Outer.this, inside every inner class instance, and that field is what lets the inner class reach back into its enclosing object's fields and methods, including private ones.
class BankAccount {
private double balance = 1000;
class StatementGenerator { // non-static inner class — tied to one BankAccount
void printStatement() {
System.out.println("Current balance: " + balance); // reaches the outer field directly
}
}
StatementGenerator createStatement() {
return new StatementGenerator(); // 'this' BankAccount is captured implicitly
}
}
BankAccount account = new BankAccount();
BankAccount.StatementGenerator statement = account.createStatement();
statement.printStatement(); // Current balance: 1000.0
Because every non-static inner class instance carries this hidden reference, it can never exist without a corresponding outer object, and it consumes a small amount of extra memory for that reference, something worth knowing if you are creating very large numbers of inner-class instances.
Anonymous and Local Classes
Local classes are defined inside a method body and scoped to it, while anonymous classes go a step further, having no name at all and being declared and instantiated in a single expression, almost always to provide a one-off implementation of an interface or abstract class.
abstract class Task {
abstract void execute();
}
public class Scheduler {
void schedule(int delaySeconds) {
// Local class — scoped to this method only
class DelayedTask extends Task {
@Override
void execute() {
System.out.println("Running after " + delaySeconds + "s delay");
}
}
new DelayedTask().execute();
// Anonymous class — same idea, but written inline, with no name
Task immediate = new Task() {
@Override
void execute() {
System.out.println("Running immediately");
}
};
immediate.execute();
}
}
Both local and anonymous classes can only capture local variables from their enclosing method if those variables are final or effectively final, never reassigned after their first value is set. This restriction exists because the inner class might genuinely outlive the method call that created it, for example if it is handed off to a separate thread, so Java captures a permanent, frozen copy of the variable's value rather than a live reference to a local stack variable that may no longer exist.
Variant | Has a Name? | Tied to an Outer Instance? | Scope |
Static nested class | Yes | No | Visible anywhere the outer class's namespace is, via Outer.Nested |
Non-static inner class | Yes | Yes, implicitly | Visible anywhere the outer class's namespace is, but needs an outer instance to construct |
Local class | Yes | Yes, if non-static method | Limited to the single method where it is declared |
Anonymous class | No | Yes, if non-static method | Limited to the single expression where it is declared |
Did You Know?
Under the hood, the Java compiler does not generate any special bytecode instructions for inner classes at all. It compiles every nested class, static or not, into its own separate .class file, named OuterClass$InnerClass.class, and quietly rewrites non-static inner classes to accept the outer instance as a hidden extra constructor parameter. Nested classes are, in this sense, a purely compile-time convenience built entirely on ordinary class files underneath.










