A class can be declared inside another class, method or expression. The four kinds differ in one key way: whether they hold a hidden reference to an enclosing instance.
The four kinds at a glance
| Kind | Declared | Outer instance? | Typical use |
|---|---|---|---|
| Static nested | static class Node inside a class | No | Helpers that belong with the outer class: Map.Entry, Builder, linked-list Node |
| Inner (member) | class Itr inside a class | Yes — Outer.this | Iterators and views that need the outer object's fields |
| Local | class X inside a method body | Yes, if in an instance method | A one-method helper type with a name |
| Anonymous | new Interface() { … } expression | Yes, if in an instance context | One-off implementation; mostly replaced by lambdas |
Static nested vs inner
public class Main {
private int size = 3;
private static String kind = "outer";
static class StaticNested { // no outer instance
String describe() { return "nested sees " + kind; } // only static members
}
class Inner { // bound to a Main instance
int size = 10;
String describe() {
return "inner " + size + ", outer " + Main.this.size;
}
}
public static void main(String[] args) {
StaticNested n = new StaticNested(); // no Main needed
System.out.println(n.describe());
Main outer = new Main();
Main.Inner in = outer.new Inner(); // needs an outer instance
System.out.println(in.describe());
}
}nested sees outer
inner 10, outer 3Local and anonymous classes
import java.util.*;
public class Main {
interface Greeter { String greet(String name); }
public static void main(String[] args) {
String greeting = "Hello"; // effectively final → can be captured
class Polite implements Greeter { // local class: has a name, lives in this method
public String greet(String name) { return greeting + ", " + name + "!"; }
}
Greeter shout = new Greeter() { // anonymous class: no name, one instance
@Override
public String greet(String name) { return greeting.toUpperCase() + " " + name; }
};
Greeter lambda = name -> greeting + " " + name; // same thing, as a lambda
System.out.println(new Polite().greet("Asha"));
System.out.println(shout.greet("Ravi"));
System.out.println(lambda.greet("Mei"));
List<String> names = new ArrayList<>(List.of("bob", "Al", "carol"));
names.sort(new Comparator<String>() { // classic pre-Java-8 style
public int compare(String a, String b) { return a.length() - b.length(); }
});
System.out.println(names);
}
}Hello, Asha!
HELLO Ravi
Hello Mei
[Al, bob, carol]Capturing variables: effectively final
Local, anonymous classes and lambdas can read local variables of the enclosing method only if they are final or effectively final (never reassigned). The value is copied into the object, so allowing later changes would make the copy and the variable disagree.
public class Main {
public static void main(String[] args) {
int count = 0;
Runnable r = () -> System.out.println(count);
count++; // makes count not effectively final
r.run();
}
}Main.java:4: error: local variables referenced from a lambda expression must be final or effectively final
Runnable r = () -> System.out.println(count);
^Anonymous class vs lambda
| Anonymous class | Lambda | |
|---|---|---|
| Implements | Any interface or abstract/concrete class, many methods | Only a functional interface (one abstract method) |
this means | The anonymous object itself | The enclosing instance |
| State | Can have fields | No fields of its own |
| Compiled to | A separate Main$1.class file | invokedynamic — no extra class file |
Static nested vs inner class — when to use which?
Why must captured local variables be effectively final?
What does this refer to inside a lambda vs an anonymous class?