Java Anonymous Class: Syntax, Scope, and Practical Use
java anonymous class: Learn how to declare and use anonymous classes in Java, including variable capture, scope, and when to prefer lambdas or local classes.
java anonymous class requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
An anonymous class in Java is a class declared at the point of instantiation without a named type. It is typically used to provide an immediate implementation of an interface or an extension of a class for a single use. Consider the common pattern of sorting a list with a custom comparator:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.length() - b.length(); } });
The expression new Comparator<String>() { ... } creates an instance of an unnamed class that implements Comparator<String>. The class body contains the method implementations required by the interface. This is the core of the java anonymous class pattern: you define and instantiate a class in one step, without a separate class declaration.
The Syntax of an Anonymous Class
An anonymous class is written as new Type() { body }, where Type is either an interface or a class. If Type is an interface, the anonymous class implements it. If Type is a class, the anonymous class extends it, optionally overriding methods. The body can contain fields, methods, and initializers, but you cannot declare a constructor because the class has no name. Instead, the constructor arguments are passed in the new expression, matching the superclass constructor.
For an interface, there is no constructor to call, so the parentheses are empty. For a class, you must provide arguments that match a constructor of the superclass. Here is an example that extends a class:
Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("Running in a thread"); } });
This creates a Runnable implementation. The same syntax applies when extending a class like ArrayList:
ArrayList<String> list = new ArrayList<String>() { @Override public boolean add(String element) { System.out.println("Adding: " + element); return super.add(element); } };
Here the anonymous class extends ArrayList<String> and overrides add to log each insertion. The constructor arguments are passed to ArrayList's no-argument constructor, which is implicit when no parentheses are used.
Capturing Variables and the Effectively Final Rule
An anonymous class can access local variables from the enclosing scope, but only if those variables are effectively final. A variable is effectively final if it is not reassigned after initialization. This rule exists because the anonymous class instance may outlive the method that created it, and Java needs to copy the variable's value into the instance. If the variable were mutable, the copy would become stale.
int base = 10; Runnable r = new Runnable() { @Override public void run() { System.out.println(base); // OK, base is effectively final } };
If you try to reassign base later, the compiler rejects the anonymous class reference. This restriction also applies to lambda expressions, but lambdas are more concise when the body is simple. The effectively final rule is a common source of confusion, especially when developers expect to modify a counter or accumulator inside a callback.
Using this Inside an Anonymous Class
Inside an anonymous class, this refers to the anonymous class instance, not the enclosing object. This is a frequent mistake when you need to call a method of the outer class. For example:
public class Outer { private String name = "outer"; void createRunnable() { Runnable r = new Runnable() { @Override public void run() { System.out.println(this.name); // error: no field 'name' in anonymous class } }; } }
To access the outer instance, you must qualify it with the outer class name: Outer.this.name. This syntax is specific to inner classes and anonymous classes. In a lambda expression, this refers to the enclosing instance, which is one of the behavioral differences between the two constructs.
Anonymous Class vs. Lambda Expression
When the interface you are implementing has a single abstract method (a functional interface), a lambda expression is usually a better fit. Lambdas are more concise and do not generate a separate class file. However, lambdas cannot have fields or instance initializers, and they cannot have multiple methods. Anonymous classes can implement interfaces with multiple methods, though that is rare and often a sign of a design issue.
The following table summarizes the key differences:
| Feature | Anonymous Class | Lambda Expression |
|---|---|---|
| Syntax | new Type() { ... } | (args) -> body |
this reference | Refers to the anonymous instance | Refers to the enclosing instance |
| Fields and initializers | Allowed | Not allowed |
| Multiple methods | Can implement all interface methods | Only one abstract method |
| Class file generation | Generates a separate .class file | Uses invokedynamic (Java 8+) |
| Variable capture | Effectively final | Effectively final |
Use a lambda when the interface is functional and the logic is short. Use an anonymous class when you need to define state (fields) or when the interface has multiple methods. In modern Java, lambdas cover most use cases that historically required anonymous classes.
Local and Inner Classes: When to Choose Them
Anonymous classes are a special case of inner classes. A local class is declared inside a method and has a name. An inner class is a member of the enclosing class. The choice among these depends on reusability and readability. If you need the same behavior in multiple places, a named local or inner class avoids duplication. An anonymous class is best for a one-off implementation that is clear at the call site.
For example, a local class can be defined and instantiated multiple times within the same method:
class Greeter { void greet() { class HelloRunnable implements Runnable { @Override public void run() { System.out.println("Hello"); } } Runnable r = new HelloRunnable(); new Thread(r).start(); } }
This is more verbose but gives the class a name for debugging and allows reuse within the method. An anonymous class is shorter but appears only once. For production code, if the implementation is complex or likely to change, a named class is easier to test and maintain.
Memory and Runtime Characteristics
Each anonymous class definition generates a separate .class file when compiled. This increases the number of classes loaded at runtime, which can affect startup time and memory footprint in large applications. However, the impact is usually negligible unless you create thousands of anonymous classes. The instance itself is a normal object; it occupies heap memory and is garbage-collected when no longer referenced.
One subtle runtime detail is that an anonymous class holds an implicit reference to the enclosing instance if it accesses any instance members of the outer class. This can cause memory leaks if the anonymous class instance is stored in a long-lived collection while the outer object becomes unreachable. For example, registering a listener as an anonymous class that captures the outer this prevents the outer object from being garbage-collected until the listener is removed. This is a common contributor to memory leaks in GUI applications. Using a static nested class or a lambda that does not capture the outer instance avoids this issue.
Common Pitfalls and Compatibility Notes
Anonymous classes have been part of Java since version 1.1, so they are compatible with all modern Java versions. However, they are not serializable by default. If you need to serialize an instance of an anonymous class, you must explicitly implement Serializable, but doing so is risky because the generated class name includes a synthetic counter, making serialized forms fragile across code changes.
Another pitfall is shadowing. If an anonymous class declares a field with the same name as a field in the enclosing scope, the anonymous class field shadows the outer field. This can lead to subtle bugs. The same applies to method parameters. Always use distinct names to avoid confusion.
Finally, anonymous classes cannot be abstract, and they cannot have static initializers or static members (except constant variables). If you need static state, a named nested class is the correct choice. These constraints are part of the language specification and have not changed across Java versions.