Anonymous Classes

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 61 of 133

An anonymous class is a class without a name, declared and instantiated in a single expression. It either implements an interface or extends a class, and it is used exactly once, at the point where it is written. Anonymous classes were the standard way to pass behavior around in Java before lambdas arrived in Java 8, and they remain the right tool when the implementation needs more than a lambda can offer: several methods, its own state, or a superclass to extend.

Syntax

Type variable = new Type(constructorArgs) {
    // fields, methods, and overrides of Type
};
  • Type is the interface being implemented or the class being extended.
  • The parentheses hold arguments for the superclass constructor; for an interface they are always empty.
  • The body in braces defines the class.
  • The whole thing is an expression, so the statement ends with ; after the closing brace.

Implementing an Interface

public interface Greeter {
    String greet(String name);
}
Greeter formal = new Greeter() {
    @Override
    public String greet(String name) {
        return "Good morning, " + name + ".";
    }
};

System.out.println(formal.greet("Dr. Rao"));   // Good morning, Dr. Rao.

This single expression does three things: declares a new class that implements Greeter, creates one instance of it, and assigns that instance to formal. The class has no name you can refer to; the compiler names it something like Main$1.

Without an anonymous class, the same code would need a separate named class:

class FormalGreeter implements Greeter {
    public String greet(String name) { return "Good morning, " + name + "."; }
}
Greeter formal = new FormalGreeter();

An anonymous class is worthwhile when the implementation is short and used in only one place.

Extending a Class

An anonymous class can extend a concrete or abstract class, passing arguments to its constructor:

public abstract class Task {
    private final String name;

    protected Task(String name) { this.name = name; }

    public abstract void run();

    public void execute() {
        System.out.println("Starting " + name);
        run();
        System.out.println("Finished " + name);
    }
}
Task backup = new Task("nightly-backup") {       // "nightly-backup" goes to Task(String)
    @Override
    public void run() {
        System.out.println("  copying files...");
    }
};

backup.execute();

Output:

Starting nightly-backup
  copying files...
Finished nightly-backup

Anonymous classes cannot declare constructors (they have no name to give one), so any setup goes through the superclass constructor arguments, field initializers, or an instance initializer block.

Capturing Variables from the Enclosing Scope

An anonymous class can use the enclosing method's local variables and parameters, as long as they are effectively final (never reassigned):

public static Greeter greeterFor(String language) {
    String prefix = switch (language) {
        case "ta" -> "Vanakkam";
        case "hi" -> "Namaste";
        default -> "Hello";
    };

    return new Greeter() {
        @Override
        public String greet(String name) {
            return prefix + ", " + name + "!";       // captures prefix
        }
    };
}

System.out.println(greeterFor("ta").greet("Priya"));   // Vanakkam, Priya!

The anonymous object outlives the method call that created it. Java copies prefix into the object, which is why the variable must not change afterward: a copy that silently diverged from the original would be confusing. Reassigning prefix anywhere in the method makes the code fail to compile.

Inside an instance method, an anonymous class can also access the enclosing object's fields and methods, because it holds a reference to that object, just as an inner class does.

State and Multiple Methods

Unlike a lambda, an anonymous class can have fields and implement more than one method:

import java.util.Iterator;

public static Iterator<Integer> countdown(int from) {
    return new Iterator<>() {
        private int current = from;            // its own mutable state

        @Override
        public boolean hasNext() {
            return current > 0;
        }

        @Override
        public Integer next() {
            return current--;
        }
    };
}

Iterator<Integer> it = countdown(3);
while (it.hasNext()) {
    System.out.print(it.next() + " ");          // 3 2 1
}

Iterator has two abstract methods, so a lambda cannot implement it. The anonymous class keeps current as a field and updates it on each call. Note new Iterator<>(): since Java 9, the diamond operator can be used with anonymous classes.

this Inside an Anonymous Class

Inside an anonymous class, this refers to the anonymous object, not the enclosing one. To reach the enclosing object, use OuterClass.this:

public class Download {
    private final String file = "report.pdf";

    public Runnable progressPrinter() {
        return new Runnable() {
            @Override
            public void run() {
                System.out.println(this.getClass().getName());   // Download$1
                System.out.println(Download.this.file);           // report.pdf
            }

            @Override
            public String toString() {
                return "progress for " + file;                   // outer field still accessible
            }
        };
    }
}

Lambdas behave differently: a lambda does not create a new scope for this, so this inside a lambda means the enclosing object.

Anonymous Classes Versus Lambdas

When an interface has exactly one abstract method (a functional interface), a lambda is usually the better choice:

// Anonymous class
Comparator<String> byLength = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
};

// Lambda: same behavior
Comparator<String> byLength2 = (a, b) -> Integer.compare(a.length(), b.length());

// Method reference with a factory: clearest of all
Comparator<String> byLength3 = Comparator.comparingInt(String::length);
Anonymous classLambda
Can implementAny interface or class, with any number of methodsOnly a functional interface (one abstract method)
Can extend a classYesNo
Fields and stateYesNo (only captured, effectively final variables)
Meaning of thisThe anonymous objectThe enclosing object
Creates a class fileYes (Outer$1.class)No separate class file; generated at runtime
VerbosityHighLow

Use a lambda whenever it fits. Reach for an anonymous class when you need to extend a class, implement several methods, hold state, or refer to the object itself with this.

Common Uses

Event listeners in GUI code (Swing, JavaFX, Android) historically used anonymous classes, especially when a listener interface has several methods:

window.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        saveDraft();
        System.exit(0);
    }
});

WindowAdapter is an abstract class with empty implementations of every WindowListener method, so the anonymous subclass overrides only the one it needs.

Adapting an abstract class once, as in the Task example above.

Test doubles: a quick fake implementation of an interface inside a test:

PaymentGateway alwaysDeclines = new PaymentGateway() {
    @Override public boolean charge(String card, double amount) { return false; }
    @Override public void refund(String txId) { }
};

Limitations and Cautions

  • No constructors. Initialization uses superclass constructor arguments, field initializers, or an instance initializer block.
  • One instance per evaluation. Each time the expression runs, a new object is created; the class itself cannot be reused elsewhere by name.
  • Hidden outer reference. An anonymous class created in an instance method keeps the enclosing object alive. A long-lived anonymous listener can therefore leak its outer object, exactly like an inner class.
  • Readability. Long anonymous classes nested inside method calls are hard to read. If the body grows beyond a few lines or is needed in two places, turn it into a named class.

Double-brace initialization: avoid it

You may see this idiom in older code:

List<String> list = new ArrayList<>() {{
    add("a");
    add("b");
}};

It creates an anonymous subclass of ArrayList with an instance initializer block. It works, but it creates an extra class for every use, captures the enclosing object (risking leaks), and can break equals and serialization. Use List.of("a", "b") or new ArrayList<>(List.of("a", "b")) instead.

Practical Example: Validation Rules

import java.util.ArrayList;
import java.util.List;

public class SignupValidation {
    interface Rule {
        String name();
        boolean test(String value);
        default String message() { return name() + " failed"; }
    }

    static Rule minLength(int n) {
        return new Rule() {
            public String name() { return "min length " + n; }
            public boolean test(String v) { return v.length() >= n; }
        };
    }

    static Rule notIn(List<String> banned) {
        return new Rule() {
            private int rejections = 0;                  // per-rule state

            public String name() { return "not a common password"; }
            public boolean test(String v) {
                boolean ok = !banned.contains(v.toLowerCase());
                if (!ok) rejections++;
                return ok;
            }
            @Override
            public String message() {
                return "too common (rejected " + rejections + " time(s))";
            }
        };
    }

    public static void main(String[] args) {
        List<Rule> rules = List.of(
            minLength(8),
            notIn(List.of("password", "12345678", "qwerty123")),
            new Rule() {
                public String name() { return "contains a digit"; }
                public boolean test(String v) { return v.chars().anyMatch(Character::isDigit); }
            }
        );

        for (String candidate : List.of("abc", "Password", "Gocourse2026")) {
            List<String> problems = new ArrayList<>();
            for (Rule r : rules) {
                if (!r.test(candidate)) problems.add(r.message());
            }
            System.out.println(candidate + " -> " + (problems.isEmpty() ? "OK" : problems));
        }
    }
}

Output:

abc -> [min length 8 failed, contains a digit failed]
Password -> [too common (rejected 1 time(s)), contains a digit failed]
Gocourse2026 -> OK

Rule has three methods (one with a default), so it is not a functional interface and cannot be implemented by a lambda. Each anonymous class supplies name and test, capturing the method parameter (n or banned) that configures it. The notIn rule keeps a private counter and overrides the default message(), things a lambda could not do. The third rule is written inline because it is used only in this one place.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Anonymous Classes and get answers drawn from it.

Signed-in readers only.