Java Inner Classes
›Short versionOpen if you only have a minute.
- A non-static inner class instance carries a hidden reference to an enclosing instance and can read its private members.
- A static nested class does not carry that reference. It is the right shape for a builder or a small helper type.
- You construct an inner instance with outer.new Inner(), or from an instance method of the outer class.
- A local class is declared inside a method and can use effectively final locals from that method.
- An anonymous class is a nameless subclass created at the new expression. In an instance method it also captures the outer object.
- A long-lived inner instance keeps the outer object alive. That hidden reference is the leak.
A ledger has lines. A line without its ledger is an incomplete thought: the line needs the ledger's currency and its private total. An is a type that is allowed to see that private state, because each line is built already holding its .
The line knows its ledger
public class Ledger {
private int total;
public class Entry {
private final int amount;
public Entry(int amount) {
this.amount = amount;
total += amount;
}
public int amount() {
return amount;
}
}
public Entry record(int amount) {
return new Entry(amount);
}
}Entry assigns total even though that field is private on Ledger. The compiler allows it because Entry is an inner class. From outside, with a ledger already in hand, the explicit form is ledger.new Entry(20). There is no new Ledger.Entry(20) that works, because that would not say which ledger the entry belongs to.
The compiled name is Ledger$Entry. The source name is still Ledger.Entry. What matters at runtime is the hidden field pointing at the ledger. As long as some cache holds the entry, that ledger stays reachable.
A helper that does not need the outer object
A is namespaced inside the outer class and stops there. The builder below can see a private constructor on the outer class, and it does not pin a half-built order in a hidden field.
public class Order {
private final String sku;
private Order(String sku) {
this.sku = sku;
}
public static class Builder {
private String sku;
public Builder sku(String sku) {
this.sku = sku;
return this;
}
public Order build() {
return new Order(sku);
}
}
}Callers write new Order.Builder(). No order exists yet, so an inner class would have been the wrong tool: there is no enclosing instance to attach. Static nested classes are also the usual home for a small key type or a parser result that is meaningful next to the outer class and useless as a top-level name.
Types that exist for one block
A stays inside the method. An does not even take a name. Both may read local variables that are never reassigned after they are captured.
Comparator<String> byLength = new Comparator<String>() {
@Override
public int compare(String left, String right) {
return Integer.compare(left.length(), right.length());
}
};That comparator is a subclass of Comparator with one instance. If this expression sits in an instance method, it also captures the enclosing instance, even though compare never uses it. A lambda has the same capture rule for local variables and is the shorter tool when the interface has a single abstract method and you need no fields of your own. Keep the anonymous class when you must implement two methods, or hold state, or extend a class.
$ javac Ledger.java$ java Ledger
You cache a Ledger.Entry in a static list, then drop your Ledger variable. Can the ledger be garbage-collected?
No. The entry is a non-static inner instance, so it points at the ledger that created it. The static list points at the entry, and the entry points at the ledger. Use a static nested class that stores only the amount if the cache should not keep the ledger.