Nested, Inner and Anonymous Classes in Java
A class declared inside another can be static, inner, local or anonymous, and the difference is mainly what each one can see.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The four kinds
| Kind | Declared | Holds outer instance | Typical use |
|---|---|---|---|
| Static nested | In a class, with static | No | A helper tied to the outer type |
| Inner (non static) | In a class, without static | Yes | Needs the outer object state |
| Local | Inside a method | Yes, if in an instance method | Used only in that method |
| Anonymous | At the point of use | Yes, if in an instance context | A one off implementation |
Static nested class
public class Catalogue {
private final List<Entry> entries = new ArrayList<>();
public void add(String title, double price) {
entries.add(new Entry(title, price));
}
public static class Entry { // no link to any Catalogue instance
private final String title;
private final double price;
Entry(String title, double price) {
this.title = title;
this.price = price;
}
}
}Catalogue.Entry entry = new Catalogue.Entry("Java notes", 250);It is an ordinary class that happens to be scoped inside another, which signals that the two belong together. This is the form to reach for by default.
Inner class
public class Library {
private final String name;
public Library(String name) {
this.name = name;
}
public class Membership { // each instance belongs to one Library
private final String holder;
public Membership(String holder) {
this.holder = holder;
}
public String describe() {
return holder + " at " + name; // reads the outer field directly
}
}
}Library library = new Library("City Library");
Library.Membership card = library.new Membership("Arun"); // note the syntax
System.out.println(card.describe()); // Arun at City LibraryAn inner class instance keeps a hidden reference to its enclosing object. That is convenient, but it also means the outer object cannot be collected while the inner one is alive, which is a classic source of memory retention. Use static unless the outer state is genuinely needed.Reaching the outer instance
public class Outer {
private String label = "outer";
class Inner {
private String label = "inner";
void show() {
System.out.println(label); // inner
System.out.println(this.label); // inner
System.out.println(Outer.this.label); // outer
}
}
}Local class
public List<String> report(List<String> rows, String prefix) {
class Formatter { // visible only inside this method
String format(String row) {
return prefix + ": " + row; // captures an effectively final local
}
}
Formatter formatter = new Formatter();
return rows.stream().map(formatter::format).toList();
}A local class can capture local variables that are final or effectively final. It is rarely needed now, because a lambda usually says the same thing more briefly.
Anonymous class
Comparator<String> byLength = new Comparator<>() {
@Override
public int compare(String a, String b) {
return Integer.compare(a.length(), b.length());
}
};The class has no name, is declared and instantiated in one expression, and can implement an interface or extend a class. Before Java 8 this was the standard way to pass behaviour.
Anonymous class compared with lambda
Comparator<String> anonymous = new Comparator<>() {
@Override public int compare(String a, String b) {
return a.compareTo(b);
}
};
Comparator<String> lambda = (a, b) -> a.compareTo(b);
Comparator<String> shorter = Comparator.naturalOrder();| Aspect | Anonymous class | Lambda |
|---|---|---|
| Abstract methods | Any number | Exactly one |
| Can extend a class | Yes | No, interfaces only |
| Own state | Yes, may declare fields | No |
this refers to | The anonymous instance | The enclosing instance |
| Class file | A separate one is generated | Linked at runtime |
Use a lambda for a single method interface. Keep anonymous classes for the cases a lambda cannot express: extending a class, adding state, or implementing more than one method.
A practical use of nesting
public final class SearchResult {
private final List<Match> matches;
private SearchResult(List<Match> matches) {
this.matches = List.copyOf(matches);
}
public record Match(long noteId, String snippet, double score) { }
public static SearchResult of(List<Match> matches) {
return new SearchResult(matches);
}
}Match has no meaning outside a search result, so nesting it states that and keeps the package tidy.
Common mistakes
- Using an inner class where a static nested one would do, and retaining the outer object unnecessarily.
- Forgetting the
outer.new Inner()syntax and tryingnew Outer.Inner(). - Expecting
thisinside an anonymous class to mean the enclosing object. - Declaring an inner class inside a long lived object such as a listener registry, and leaking the whole outer graph.
- Writing an anonymous class for a single method interface where a lambda is clearer.
Best practices
- Prefer
staticnested classes; add the outer link only when the state is needed. - Nest a class when it is meaningless outside its enclosing type, and keep it
privatewhen possible. - Use a record for a nested data carrier.
- Reach for a lambda first, and an anonymous class only when a lambda cannot express it.
Practice
- Convert an inner class to a static nested class and list what had to change.
- Why does
new Library.Membership("Arun")fail to compile? - Print both the inner and outer field of the same name from inside an inner class.
- Rewrite an anonymous
Runnableas a lambda and explain whatthisnow refers to. - Describe a realistic case where an inner class causes a memory leak.
Conclusion
Nest a class when it belongs to its enclosing type. Make it static unless the outer instance is genuinely required, and let lambdas replace the anonymous classes that only implement one method.