Inner classes in Java
Tied to an outer instance; Outer.this; memory-leak risk.
Glued to an outer object
An inner class is a nested class without static. Every inner object belongs to an outer instance and keeps a hidden reference to it. That's why inner code can use the outer's fields directly.
class Outer {
int x = 1;
class Inner {
int get() { return x; } // Outer's x
}
}Creating one & Outer.this
From outside you need an outer object first: **outer.new Inner()**. When names clash, plain x means the inner's own field, and **Outer.this.x** reaches the enclosing object's field.
Outer o = new Outer();
Outer.Inner in = o.new Inner();
// or: new Outer().new Inner()Shadowed names
Both classes have a field n. What prints?
class Box {
int n = 3;
class Lid {
int n = 5;
int f() { return n * 100 + Box.this.n; }
}
}
void main() {
System.out.println(new Box().new Lid().f());
}503305505303
Show the answer
Plain n is Lid's own field (5) — it shadows Box's. Box.this.n is the enclosing object's field (3). 5 * 100 + 3 = 503.
No outer, no inner
Writing new Outer.Inner() with no outer instance is a compile error — the inner object would have nobody to point to. Write new Outer().new Inner(), or make the class static if it doesn't need the outer object.
class Outer { class Inner { } }
var i = new Outer.Inner(); // error
var j = new Outer().new Inner(); // okThe leaking screen
A screen holding 50 MB of images registers an *inner-class* listener with an app-wide event bus that lives forever. The screen closes, but memory never drops. Why — and what's the cleanest fix?
Think about it, then reveal the answer
The bus keeps the listener alive, and the listener's hidden Outer.this keeps the whole screen reachable, images included. Fix: make the listener a static nested class that receives only the data it needs (and unregister it when the screen closes).
A famous warning
Android developers met this leak so often that Android's lint tool warns "This Handler class should be static or leaks might occur". The same bug appears in servers with long-lived caches and schedulers that hold inner-class tasks.
Key takeaways
- outer.new Inner() — needs an outer instance
- Outer.this.x reaches the outer field when names clash
- Every inner object holds a hidden reference to its outer object
- Leak risk: a long-lived inner object keeps its outer alive
💡 A balloon tied to a child's wrist: wherever the balloon goes, the child is still attached.
Inner classes arrived in Java 1.1 (1997) without changing the JVM: javac quietly adds a hidden field, named this$0, that stores the outer object.
Practice questions
What does this print?
class Outer {
int x = 1;
class Inner {
int x = 2;
int f() { return x * 10 + Outer.this.x; }
}
}
void main() {
System.out.println(new Outer().new Inner().f());
}- 21
- 22
- 12
- 11
Check your answer
21. Plain x is Inner's own field (2); Outer.this.x is the enclosing object's field (1). 2 * 10 + 1 = 21.
What does this print?
class Outer {
class Inner { }
}
void main() {
var i = new Outer.Inner();
System.out.println("made");
}- made
- Compile error
- Throws NullPointerException
Check your answer
Compile error. Inner needs an enclosing Outer object, and none is given. new Outer().new Inner() would work.