☕ Java

Anonymous Inner Classes

An anonymous inner class is a class that is declared and instantiated in a single expression. It has no name and cannot be referenced by type anywhere other than the point of its creation. Anonymous classes extend a class or implement an interface and provide the body immediately in the same expression that creates the single instance. They were the idiomatic way to pass behaviour as data in Java before lambda expressions were introduced in Java 8. Understanding them remains essential because much existing code uses them, because they provide capabilities lambdas do not, and because they clarify why lambdas exist. This entry covers the syntax and mechanics of anonymous classes, their access rules, when they are still preferred over lambdas, and how the two mechanisms compare.

Anonymous Class Syntax and Mechanics

An anonymous class expression consists of the new keyword, the name of a class to extend or an interface to implement, parentheses for the constructor arguments, and a class body enclosed in braces. The entire expression is followed by a semicolon (if it is a statement) or used directly as a value (if it is an expression). The result is an instance of the unnamed class. An anonymous class can extend exactly one class or implement exactly one interface — never both, and never more than one interface. When extending a class, constructor arguments are passed in the parentheses. When implementing an interface (which has no constructor), the parentheses are always empty. The anonymous class body can contain fields, methods, and an instance initialization block (since it has no named constructor). The anonymous class is compiled to a named class file — the compiler generates a synthetic name like OuterClass$1.class, OuterClass$2.class, and so on, numbered in the order they appear in the source. This class has the same access rules as a regular inner class: it carries an implicit reference to the enclosing instance and can access all members of the enclosing class, including private ones. It can also access effectively final local variables from the enclosing scope, just like local inner classes. Every anonymous class expression creates exactly one instance. If two instances are needed, the anonymous class must either be created twice with two separate expressions, or promoted to a named inner class that can be instantiated multiple times.
Java
// ── Anonymous class implementing an interface ────────────────────────
Comparator<String> byLength = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
};

List<String> words = new ArrayList<>(
    List.of("banana", "apple", "fig", "cherry"));
Collections.sort(words, byLength);
System.out.println(words);   // [fig, apple, banana, cherry]

// ── Anonymous class extending a concrete class ────────────────────────
Thread t = new Thread("worker") {   // extends Thread, passes name to constructor
    @Override
    public void run() {
        System.out.println("Running in: " + getName());
    }
};
t.start();

// ── Anonymous class with instance initializer and fields ─────────────
// (Cannot have a named constructor — use instance block instead)
Runnable withState = new Runnable() {

    private final String config;
    private       int    runCount = 0;

    // Instance initializer replaces constructor
    {
        config = System.getProperty("app.mode", "default");
        System.out.println("Anonymous Runnable created, mode=" + config);
    }

    @Override
    public void run() {
        runCount++;
        System.out.println("Run #" + runCount + " mode=" + config);
    }
};

withState.run();   // Run #1 mode=default
withState.run();   // Run #2 mode=default

// ── Anonymous class as method argument ───────────────────────────────
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button clicked: " + e.getActionCommand());
    }
});

// ── Accessing outer class members and local variables ─────────────────
public class OuterDemo {
    private String outerField = "outer";

    public Runnable makeTask(String taskName) {  // effectively final
        return new Runnable() {
            @Override
            public void run() {
                // Access outer field (implicit OuterDemo.this reference)
                System.out.println(outerField + " - " + taskName);
            }
        };
    }
}

Anonymous Classes vs Lambda Expressions

Lambda expressions, introduced in Java 8, replaced anonymous classes for the most common use case: implementing a functional interface (an interface with exactly one abstract method). A lambda is a concise expression of the same concept — passing behaviour as data — without the syntactic overhead of the class declaration. Where an anonymous Comparator requires twelve lines, a lambda requires one. The differences between lambdas and anonymous classes are deeper than syntax. An anonymous class is a distinct class with its own namespace, its own this reference (referring to the anonymous class instance), and its own access to the enclosing outer class through OuterClass.this. A lambda is not a class at all — it is syntactic sugar for a method. The lambda's this refers to the enclosing class instance, not to any lambda object. Inside a lambda, this.field means the outer class's field. Inside an anonymous class, this.field means the anonymous class's own field. Anonymous classes retain several advantages over lambdas. They can implement interfaces with more than one abstract method. They can carry state in instance fields. They can extend a concrete class rather than only implementing an interface. They can be instantiated multiple times from one declaration (by naming the class with a local class rather than an anonymous class, but the point stands). For any of these needs, anonymous classes (or named inner classes) remain the right tool.
Java
// ── Same behaviour: anonymous class vs lambda ────────────────────────
// Before Java 8 — anonymous class
Comparator<String> byLengthAnon = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
};

// Java 8+ — lambda (same semantics, 90% less code)
Comparator<String> byLengthLambda =
    (a, b) -> Integer.compare(a.length(), b.length());

// Even more concise with method reference
Comparator<String> byLengthRef = Comparator.comparingInt(String::length);

// ── 'this' semantics — the critical difference ────────────────────────
public class ThisDemo {

    private String name = "OuterClass";

    public void demonstrate() {
        // ── Anonymous class: 'this' refers to the anonymous instance ──
        Runnable anon = new Runnable() {
            private String name = "AnonymousClass";

            @Override
            public void run() {
                System.out.println(this.name);             // "AnonymousClass"
                System.out.println(ThisDemo.this.name);    // "OuterClass"
            }
        };
        anon.run();

        // ── Lambda: 'this' refers to the enclosing ThisDemo instance ──
        Runnable lambda = () -> {
            System.out.println(this.name);  // "OuterClass" — lambda's 'this'
            // Cannot write ThisDemo.this.name — same thing, no ambiguity
        };
        lambda.run();
    }
}

// ── When to use anonymous class (cannot replace with lambda) ──────────

// Case 1: Interface with multiple abstract methods (not functional)
MouseAdapter complexHandler = new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e)  { /* handle click  */ }
    @Override
    public void mouseEntered(MouseEvent e)  { /* handle enter  */ }
    @Override
    public void mouseExited(MouseEvent e)   { /* handle exit   */ }
    // MouseAdapter provides empty defaults for all MouseListener methods
};

// Case 2: Need instance fields to maintain state
Iterator<Integer> statefulIterator = new Iterator<>() {
    private int current = 0;
    private int step    = 2;

    @Override
    public boolean hasNext() { return current < 100; }

    @Override
    public Integer next() {
        int val = current;
        current += step;
        return val;
    }
};

// Case 3: Extending a concrete class
Thread priorityTask = new Thread("priority-task") {
    @Override
    public void run() { System.out.println("High priority: " + getName()); }
    // Cannot do this with a lambda — Thread is a class, not an interface
};

Practical Patterns with Anonymous Classes

Anonymous classes shine in three practical scenarios that lambdas cannot cover. First, when implementing multi-method interfaces or abstract classes — event adapters, lifecycle callbacks, configuration objects. Second, when the implementation needs instance state — a counter, a cache, a configurable parameter that the implementation reads during execution. Third, when extending an abstract class or concrete class rather than implementing an interface. The double-brace initialization idiom — creating a collection with an anonymous subclass that adds elements in an instance initializer — became popular for its conciseness but is generally discouraged. It creates a new unnamed class for every collection literal, which bloats the class count, prevents equals() from working correctly across instances of the same anonymous type, and keeps an implicit reference to the enclosing object alive. Modern Java's List.of(), Set.of(), and Map.of() factory methods provide a better alternative. Test mocking with anonymous classes was common before mocking frameworks and lambda expressions. Creating a fake repository or a stub service as an anonymous class in a test is still occasionally cleaner than configuring a mocking framework for simple scenarios.
Java
// ── Pattern 1: Adapting a multi-method abstract class ─────────────────
// WindowAdapter provides empty implementations of all WindowListener methods
// Anonymous class overrides only the ones needed
frame.addWindowListener(new WindowAdapter() {
    @Override
    public void windowClosing(WindowEvent e) {
        // Prompt to save changes before closing
        if (hasUnsavedChanges()) {
            int choice = JOptionPane.showConfirmDialog(
                frame, "Save changes before closing?");
            if (choice == JOptionPane.YES_OPTION) saveChanges();
        }
        System.exit(0);
    }

    @Override
    public void windowIconified(WindowEvent e) {
        System.out.println("Window minimized — pausing animations");
    }
    // All other methods inherited as empty stubs from WindowAdapter
});

// ── Pattern 2: Stateful behaviour in anonymous class ─────────────────
// A throttled event handler that ignores events within 500ms of the last
long minIntervalMs = 500;
ActionListener throttled = new ActionListener() {
    private long lastFired = 0;

    @Override
    public void actionPerformed(ActionEvent e) {
        long now = System.currentTimeMillis();
        if (now - lastFired >= minIntervalMs) {
            lastFired = now;
            processEvent(e);   // only fires if enough time has passed
        }
    }
};

// ── Pattern 3: Test stub with anonymous class ─────────────────────────
// Quick stub without a mocking framework
@Test
void processOrder_chargesPayment() {
    List<PaymentRequest> charged = new ArrayList<>();

    // Anonymous class stub captures what was charged
    PaymentProcessor stub = new PaymentProcessor() {
        @Override
        public PaymentResult charge(PaymentRequest request) {
            charged.add(request);
            return PaymentResult.success("tx-001");
        }
        @Override
        public PaymentResult refund(String txId, BigDecimal amount) {
            return PaymentResult.success("refund-001");
        }
        @Override
        public PaymentStatus getStatus(String txId) {
            return PaymentStatus.COMPLETED;
        }
    };

    OrderService service = new OrderService(stub);
    service.processOrder(buildOrder());

    assertThat(charged).hasSize(1);
    assertThat(charged.get(0).getAmount())
        .isEqualByComparingTo("99.99");
}

// ── Anti-pattern: double-brace initialization — avoid ─────────────────
// AVOID — creates anonymous subclass, prevents equals(), leaks outer ref
Map<String, Integer> bad = new HashMap<String, Integer>() {{
    put("a", 1);
    put("b", 2);
}};

// PREFER — factory method, no anonymous class, correct equals()
Map<String, Integer> good = new HashMap<>(Map.of("a", 1, "b", 2));

Related Topics in Object-Oriented Programming

OOP Concepts
Object-Oriented Programming (OOP) is a programming paradigm that organises software around objects — self-contained units that combine data (fields) and behaviour (methods). Java is a class-based, object-oriented language where almost everything is an object. OOP provides four foundational principles: encapsulation, inheritance, polymorphism, and abstraction. Together they produce software that is modular, reusable, maintainable, and easier to reason about as systems grow in complexity.
Classes
A class is the fundamental building block of Java. It is a blueprint that defines the structure and behaviour of objects — what data each object holds (fields) and what operations it can perform (methods). Every Java program is composed of classes. Understanding how to design a class well — choosing the right access modifiers, separating state from behaviour, and writing cohesive single-responsibility classes — is the foundation of object-oriented programming in Java. This entry covers class anatomy, fields, methods, access modifiers, static vs instance members, the this keyword, and class design principles.
Objects
An object is a runtime instance of a class. Where a class is a blueprint that exists in source code, an object is a living entity that exists in memory during program execution — it has its own identity, its own state stored in its fields, and the ability to respond to method calls. Every object has three fundamental properties: identity (a unique memory address), state (the current values of its fields), and behaviour (the methods it responds to). This entry covers object identity vs equality, the Object class hierarchy, object state and mutation, method calls, toString, equals and hashCode, and the object lifecycle.
Object Creation
Object creation in Java is the process of allocating memory, initialising fields, and running constructor logic to bring an object into existence. The new keyword is the primary mechanism, but Java also provides factory methods, builder patterns, copy constructors, and object cloning. Constructors are special methods that set up the initial state — their design determines how easy or difficult the class is to use correctly. This entry covers constructors in depth, constructor overloading and chaining, copy constructors, factory methods, the builder pattern, and the difference between shallow and deep copy.