CodeGym /Courses /JAVA 25 SELF /Anonymous classes: how they differ from lambdas, with exa...

Anonymous classes: how they differ from lambdas, with examples

JAVA 25 SELF
Level 48 , Lesson 4
Available

1. Diving into anonymous classes

An anonymous class is a nameless subclass or interface implementation created right at the point of use. Before lambdas (Java 8) it was the most convenient way to provide a “one-off” implementation of an interface or abstract class.

A classic:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from an anonymous class!");
    }
};
r.run();

Here we declared and immediately implemented the Runnable interface — without a separate file or a class name. Such implementations were often used for event handlers, comparators, threads, and other tasks where you need to quickly “plug in” behavior.

If a lambda is an “expression on the fly”, then an anonymous class is a “little actor without a name”: it plays a bit part and disappears.

2. Comparison with lambda expressions

Syntax

Anonymous class:

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};

Lambda expression:

Comparator<String> comp = (a, b) -> a.length() - b.length();

The difference is obvious: a lambda is more compact — you don’t need to spell out types, the method name, or extra braces if the action is simple.

Functionality

  • Anonymous class — a full-fledged object. You can declare fields, extra methods, and override Object methods (toString, equals, etc.).
  • Lambda expression — an implementation of a single abstract method of a functional interface. You cannot declare your own fields or additional methods inside it.

Which to choose when?

  • Lambda — when you need a concise implementation of a single method of a functional interface.
  • Anonymous class — when you need to:
    • implement multiple methods (for example, of an abstract class);
    • declare fields for state;
    • override Object methods (for example, toString);
    • leverage inheritance/access specifics (for example, access to protected members of a superclass).

3. Scope and the this keyword

Here lies a common pitfall:

  • in an anonymous class this refers to the instance of the anonymous class;
  • in a lambda expression this refers to the enclosing class in which the lambda is declared.

Example: compare behavior

public class Outer {
    String name = "Outer class";

    void test() {
        Runnable anon = new Runnable() {
            String name = "Anonymous class";
            @Override
            public void run() {
                System.out.println(this.name); // "Anonymous class"
            }
        };
        Runnable lambda = () -> System.out.println(this.name); // "Outer class"

        anon.run();
        lambda.run();
    }
}

Output:

Anonymous class
Outer class

In an anonymous class, this points to the anonymous class itself (its name field is used). In a lambda, this is Outer.

4. When should you use anonymous classes?

If you need to implement more than one method

A lambda works only with functional interfaces (exactly one abstract method). If an interface/abstract class requires multiple methods — you need an anonymous class.

abstract class Animal {
    abstract void say();
    abstract void jump();
}

Animal cat = new Animal() {
    @Override
    void say() {
        System.out.println("Meow!");
    }
    @Override
    void jump() {
        System.out.println("Jump!");
    }
};

If you need to keep state (fields)

Runnable r = new Runnable() {
    int counter = 0;
    @Override
    public void run() {
        counter++;
        System.out.println("Called " + counter + " time(s)");
    }
};
r.run(); // Called 1 time(s)
r.run(); // Called 2 time(s)

If you need to override Object methods

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
    @Override
    public String toString() {
        return "String length comparator";
    }
};
System.out.println(comp); // String length comparator

5. Examples: Comparator and Runnable — lambda vs anonymous class

Sorting strings by length

Anonymous class:

List<String> words = Arrays.asList("cat", "elephant", "mouse", "tiger");
words.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});
System.out.println(words);

Lambda expression:

List<String> words = Arrays.asList("cat", "elephant", "mouse", "tiger");
words.sort((a, b) -> a.length() - b.length());
System.out.println(words);

The result is the same, but the lambda code is shorter and easier to read.

Runnable: starting a thread

Anonymous class:

Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread via anonymous class");
    }
});
t1.start();

Lambda expression:

Thread t2 = new Thread(() -> System.out.println("Thread via lambda"));
t2.start();

Anonymous class with fields

Runnable r = new Runnable() {
    int count = 0;
    @Override
    public void run() {
        count++;
        System.out.println("Called " + count + " time(s)");
    }
};
r.run(); // Called 1 time(s)
r.run(); // Called 2 time(s)

You can’t do this in a lambda — there’s no way to declare a field.

6. Nuances: scope, variables, and final

In both anonymous classes and lambda expressions, you can use local variables from the enclosing method only if they are final or “effectively final” (not changed after initialization). But there’s a nuance with names:

  • in an anonymous class, you can declare a variable with the same name as in the outer scope (“shadowing”);
  • in a lambda — you cannot: the name must not conflict with an outer variable’s name.

Example:

int x = 10;
Runnable r = new Runnable() {
    @Override
    public void run() {
        int x = 20; // OK: shadows the outer variable
        System.out.println(x); // 20
    }
};
r.run();

Runnable l = () -> {
    // int x = 30; // Compilation error: variable already defined
    System.out.println(x); // 10
};
l.run();

7. When is a lambda better, and when is an anonymous class indispensable?

Choose lambda expressions if:

  • you need to implement a short function for a functional interface;
  • you don’t need to keep state;
  • you don’t need to override Object methods;
  • the implementation is “here and now” and simple.

An anonymous class is necessary if:

  • you must implement an interface with multiple methods or an abstract class;
  • you need to declare fields or additional methods;
  • you need to override toString, equals, hashCode;
  • you need access to protected members of a superclass.

8. Practice: side-by-side comparison

Task 1: Filtering a list via Predicate

Anonymous class:

List<String> animals = Arrays.asList("cat", "elephant", "mouse", "tiger");
animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() < 4;
    }
});
System.out.println(animals); // [elephant, mouse, tiger]

Lambda expression:

List<String> animals = Arrays.asList("cat", "elephant", "mouse", "tiger");
animals.removeIf(s -> s.length() < 4);
System.out.println(animals); // [elephant, mouse, tiger]

Task 2: Comparing the scope of this

public class Demo {
    String name = "Demo";

    void check() {
        Runnable anon = new Runnable() {
            String name = "Anon";
            @Override
            public void run() {
                System.out.println(this.name); // "Anon"
            }
        };

        Runnable lambda = () -> System.out.println(this.name); // "Demo"

        anon.run();
        lambda.run();
    }

    public static void main(String[] args) {
        new Demo().check();
    }
}

9. Common mistakes when working with anonymous classes and lambda expressions

Error #1: Expecting a lambda to implement multiple methods. A lambda works only with functional interfaces (one abstract method). If there are more methods — use an anonymous class.

Error #2: Confusion about the scope of this. In a lambda, this is the enclosing class; in an anonymous class, it’s the anonymous class itself. Because of this, it’s easy to end up with the “wrong” fields and values.

Error #3: Trying to declare fields in a lambda. You can’t declare your own fields in a lambda — you can only use variables from the outer context (final/“effectively final”). Use an anonymous class for state.

Error #4: Variable shadowing. In an anonymous class, you can declare a local variable with the same name as an outer one — that’s shadowing. In a lambda you can’t: the compiler will report an error.

Error #5: Logic that’s too complex for a lambda. If the lambda body grows beyond 35 lines, readability suffers. It’s better to extract the code into a separate method or use an anonymous class (if you need state/multiple methods).

1
Task
JAVA 25 SELF, level 48, lesson 4
Locked
Censorship in the Digital Zoo 🦒
Censorship in the Digital Zoo 🦒
1
Task
JAVA 25 SELF, level 48, lesson 4
Locked
Inner Voices 🎭
Inner Voices 🎭
1
Survey/quiz
Lambda expressions, level 48, lesson 4
Unavailable
Lambda expressions
Lambda expressions
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION