🪆 Enums, Records & Nested Types · Intermediate

Anonymous classes in Java

One-off implementations; how they differ from lambdas (this, state).

🧩 The mysteryBefore lambdas, Java developers wrote new Runnable() { ... } thousands of times. Is that old style dead — or can it still do things lambdas can't?

Declare and create in one go

An anonymous class declares a one-off class and creates its only object in a single expression: new Interface() { ... } or new SomeClass() { ... }. It has no name, so you can't reuse it anywhere else.

Runnable r = new Runnable() {
    public void run() {
        System.out.println("running");
    }
};

What lambdas can't do

Unlike a lambda, an anonymous class can have its own fields, several methods, and can extend a class — even an abstract one — or implement an interface with two or more abstract methods. Like lambdas, it can capture effectively final locals.

🔮 Predict it

A counter with memory

What does this print?

void main() {
    var c = new Supplier<Integer>() {
        int n = 0;
        public Integer get() { return ++n; }
    };
    c.get();
    c.get();
    System.out.println(c.get());
}
  1. 3
  2. 1
  3. Compile error: anonymous classes can't have fields
Show the answer

The anonymous object **keeps its own n** between calls: 1, 2, then 3. A lambda couldn't hold that state itself.

⚠️ The trap

`this` means two different things

Inside a lambda, this is the enclosing object. Inside an anonymous class, this is the anonymous object itself. So this.id can read two different fields in code that looks almost the same.

String id = "outer";
Runnable lam = () -> print(this.id);  // outer
Runnable anon = new Runnable() {
    String id = "anon";
    public void run() { print(this.id); } // anon
};

Modernize it

✗ Old style
Comparator<String> byLen =
    new Comparator<>() {
        public int compare(String a, String b) {
            return a.length() - b.length();
        }
    };

Six lines of boilerplate — and overflow-prone subtraction.

✓ Modern
Comparator<String> byLen =
    Comparator.comparingInt(String::length);

Comparator is functional, so a factory method does the job safely.

💼 In the real world

Where you still see them

Legacy Swing and Android listeners are full of anonymous classes. Today they still earn their place when you need state or must extend an abstract class — e.g. new TimerTask() { public void run() { ... } }, because TimerTask is a class, not an interface.

Key takeaways

  1. new Runnable() { public void run() { ... } }
  2. Can hold its own fields and implement several methods
  3. this = the anonymous object (in a lambda, this = the enclosing object)
  4. Captures effectively final locals, like lambdas
🤯 Did you know?

Each anonymous class becomes its own file — Main$1.class, Main$2.class… Lambdas don't: since Java 8 they're linked at runtime through invokedynamic.

Practice questions

What does this print?

void main() {
    var counter = new Supplier<Integer>() {
        int n = 0;
        public Integer get() { return ++n; }
    };
    counter.get();
    System.out.println(counter.get());
}
  1. 2
  2. 1
  3. 0
  4. Compile error: anonymous classes can't have fields
Check your answer

2. The anonymous object keeps its own n between calls: the first get() returns 1, the second returns 2.

What does this print?

String id = "outer";
void main() {
    Runnable lam = () -> System.out.println(this.id);
    Runnable anon = new Runnable() {
        String id = "anon";
        public void run() { System.out.println(this.id); }
    };
    lam.run();
    anon.run();
}
  1. outer anon
  2. anon anon
  3. outer outer
  4. anon outer
Check your answer

outer anon. In the lambda, this is the enclosing object (id = "outer"). In the anonymous class, this is the anonymous object (id = "anon").

Next: copy an object with clone() and the copy may secretly share its insides with the original.