☕ Java

Inner Classes

An inner class is a non-static class declared inside another class. It maintains an implicit reference to an enclosing instance of the outer class and can access all members of the outer class, including private fields and methods. This bidirectional access creates a tight coupling that is sometimes exactly what is needed — an iterator that needs access to a collection's private array, an adapter that wraps private behaviour, an event handler with access to the component's private state. This entry covers inner class mechanics, the implicit outer reference, local inner classes, the disambiguation syntax when names conflict, memory implications, and the practical patterns where inner classes are the right tool.

Inner Class Mechanics and the Implicit Reference

An inner class is any nested class that is not declared static. Its defining characteristic is the implicit reference to the enclosing class instance — what the compiler names this$0. Every inner class instance is associated with exactly one enclosing instance and cannot exist without one. Creating an inner class instance requires either being inside the outer class (where this provides the enclosing reference automatically) or using the explicit outer.new syntax from outside. The implicit reference gives inner classes unrestricted access to the enclosing instance's members — all four access levels, including private. This is not a security hole; it is an intentional feature. Inner classes are implementation details of the outer class, and as such they are trusted partners that need full access to the outer class's internals. An inner class can itself have member variables with the same name as the outer class's members. When names collide, the inner class's own member shadows the outer class's member. The explicit syntax OuterClass.this.fieldName resolves the outer class's member unambiguously. Without this qualifier, the inner class's member is used. Understanding this disambiguation is important for writing correct inner class code when names overlap, which happens frequently in observer and listener patterns where both the handler and the observed component have a state field. Inner classes can be any access level — public, protected, package-private, or private. A private inner class is completely invisible outside the outer class, which allows the outer class to use the inner class as a private implementation mechanism without exposing it. This is frequently used for iterators, validators, and strategy implementations that are internal details.
Java
// ── Inner class with full access to outer class ─────────────────────
public class TextBuffer {

    private char[]  data;
    private int     length;
    private int     capacity;

    public TextBuffer(int initialCapacity) {
        this.data     = new char[initialCapacity];
        this.length   = 0;
        this.capacity = initialCapacity;
    }

    public void append(char c) {
        ensureCapacity(length + 1);
        data[length++] = c;
    }

    public void append(String s) {
        ensureCapacity(length + s.length());
        s.getChars(0, s.length(), data, length);
        length += s.length();
    }

    private void ensureCapacity(int minCapacity) {
        if (minCapacity > capacity) {
            capacity = Math.max(minCapacity, capacity * 2);
            data     = Arrays.copyOf(data, capacity);
        }
    }

    // ── Private inner classIterator that reads private data ────────
    // Private = completely invisible outside TextBuffer
    private class CharIterator implements Iterator<Character> {
        private int position = 0;

        @Override
        public boolean hasNext() {
            return position < length;  // accesses TextBuffer.length — private
        }

        @Override
        public Character next() {
            if (!hasNext()) throw new NoSuchElementException();
            return data[position++];   // accesses TextBuffer.data — private
        }
    }

    // ── Public inner class — a cursor into the buffer ─────────────────
    public class Cursor {
        private int pos;
        private int markPos = -1;

        public Cursor() { this.pos = 0; }

        public char current() {
            if (pos >= length) throw new IndexOutOfBoundsException();
            return data[pos];   // accesses TextBuffer.data
        }

        public void advance() { if (pos < length) pos++; }
        public void mark()    { markPos = pos; }
        public void reset()   { if (markPos >= 0) pos = markPos; }
        public int  position(){ return pos; }
        public int  remaining(){ return length - pos; } // TextBuffer.length
    }

    public Iterator<Character> iterator() { return new CharIterator(); }
    public Cursor cursor()                 { return new Cursor();       }
    public int    length()                 { return length;             }

    @Override
    public String toString() {
        return new String(data, 0, length);
    }
}

// ── Creating inner class instances ────────────────────────────────────
TextBuffer buffer = new TextBuffer(16);
buffer.append("Hello World");

// Inside the class: new CharIterator() — implicit outer reference
// Outside the class: buffer.new Cursor() — explicit outer reference
TextBuffer.Cursor cursor = buffer.cursor();
while (cursor.remaining() > 0) {
    System.out.print(cursor.current());
    cursor.advance();
}

Name Disambiguation with OuterClass.this

When an inner class and its outer class have fields or methods with the same name, the inner class's names shadow the outer class's names within the inner class body. Simply writing fieldName inside the inner class refers to the inner class's own field. To access the outer class's field when it is shadowed, use the explicit OuterClass.this.fieldName syntax. This disambiguation syntax is not just for resolving conflicts — it is also valuable for clarity when passing the outer or inner instance as an argument. Passing this in an inner class method passes the inner instance. Passing OuterClass.this passes the enclosing outer instance. Being explicit about which this is being passed makes code more readable and prevents subtle bugs where the wrong instance is registered as a listener. The disambiguation pattern appears most commonly in GUI programming and event handling, where a component (the outer class) registers listeners (inner classes) that need to reference both the component and themselves. The listener needs to pass its own instance to a registry and also needs to call back on the component's state — both require distinct this references.
Java
// ── Name disambiguation: OuterClass.this ────────────────────────────
public class EventSource {

    private String name;         // outer field
    private int    eventCount;   // outer field

    public EventSource(String name) {
        this.name = name;
    }

    public void fireEvent(String type) {
        eventCount++;
        new EventNotifier(type).notify();
    }

    // ── Inner class with a 'name' field that shadows outer 'name' ─────
    public class EventNotifier {

        private String name;       // SHADOWS EventSource.name
        private int    eventCount; // SHADOWS EventSource.eventCount

        public EventNotifier(String eventType) {
            this.name       = eventType;       // inner class 'name'
            this.eventCount = 1;               // inner class 'eventCount'
        }

        public void notify() {
            // ── Disambiguating inner vs outer ──────────────────────────
            System.out.println("Source: "   + EventSource.this.name);   // outer
            System.out.println("Event type: " + this.name);             // inner
            System.out.println("Total events: "
                + EventSource.this.eventCount); // outer
            System.out.println("Local count: "
                + this.eventCount);             // inner

            // ── Passing the outer or inner reference ──────────────────
            // logInner(this)              — passes EventNotifier instance
            // logOuter(EventSource.this)  — passes EventSource instance
        }
    }
}

// ── Observer pattern: inner class passes outer reference ──────────────
public class Button {

    private String  label;
    private boolean pressed;
    private final List<ActionListener> listeners = new ArrayList<>();

    public Button(String label) { this.label = label; }

    // ── Inner listener that refers back to the Button ─────────────────
    private class DoubleClickDetector implements MouseListener {

        private long lastClickTime = 0;
        private static final long DOUBLE_CLICK_MS = 300;

        @Override
        public void mouseClicked(MouseEvent e) {
            long now = System.currentTimeMillis();
            if (now - lastClickTime < DOUBLE_CLICK_MS) {
                // Pass Button.this (the enclosing Button) to the event
                listeners.forEach(l ->
                    l.actionPerformed(new ActionEvent(
                        Button.this, ActionEvent.ACTION_PERFORMED,
                        "doubleclick:" + label)));
            }
            lastClickTime = now;
        }
        // Other MouseListener methods with empty bodies...
        @Override public void mousePressed(MouseEvent e)  {}
        @Override public void mouseReleased(MouseEvent e) {}
        @Override public void mouseEntered(MouseEvent e)  {}
        @Override public void mouseExited(MouseEvent e)   {}
    }

    public void addActionListener(ActionListener l) {
        listeners.add(l);
    }
}

Local Inner Classes

A local class is declared inside a method, constructor, or initialization block. It is visible only within the enclosing block — it cannot be referenced outside the block where it is declared. Local classes can access the enclosing method's local variables provided those variables are effectively final (their value never changes after assignment). They can also access all members of the enclosing outer class, including private members, through the same implicit outer reference that regular inner classes have. Local classes are useful when a named class is needed within a single method, the class needs state and therefore cannot be expressed as a lambda, and the class is too specialised or too tightly coupled to the method's local context to be promoted to a member class. The classic use case is implementing an interface with stateful behaviour that references both the method's local variables and the enclosing object's state. The effectively final requirement for accessing local variables exists because of how the JVM implements local class access. The compiler copies the values of referenced local variables into synthetic fields of the local class at construction time. If the local variable could change after this copy, the local class would see a stale value. Requiring effectively final ensures the local variable's value never changes, so the copy is always accurate.
Java
// ── Local class — visible only inside the method ────────────────────
public class DataProcessor {

    private final String processorId;
    private       int    processedCount;

    public DataProcessor(String id) { this.processorId = id; }

    public List<String> processWithValidation(List<String> items,
                                               String rule,
                                               int maxLength) {
        // maxLength and rule are effectively final (never reassigned)

        // ── Local class — scoped to this method only ───────────────────
        class ItemFilter {
            private int rejectedCount = 0;  // local class can have state

            boolean accepts(String item) {
                // Access enclosing method's effectively-final locals
                if (item.length() > maxLength) {
                    rejectedCount++;
                    return false;
                }
                if (!item.matches(rule)) {
                    rejectedCount++;
                    return false;
                }
                return true;
            }

            int getRejectedCount() { return rejectedCount; }

            String report() {
                // Access outer class members (DataProcessor.this implicit)
                return String.format(
                    "Processor %s: accepted %d, rejected %d",
                    processorId, processedCount, rejectedCount);
            }
        }

        // ── Use the local class within the method ─────────────────────
        ItemFilter filter = new ItemFilter();
        List<String> accepted = new ArrayList<>();

        for (String item : items) {
            if (filter.accepts(item)) {
                accepted.add(item);
                processedCount++;
            }
        }

        System.out.println(filter.report());
        return accepted;
    }
    // ItemFilter is completely invisible outside processWithValidation()
}

// ── Local class with interface implementation ─────────────────────────
public class SortingDemo {

    private String locale = "en";

    public void sortWithCustomOrder(List<String> words) {
        // Local variable used in local class
        String currentLocale = locale;

        // ── Local class implementing Comparator ───────────────────────
        class LocaleAwareComparator implements Comparator<String> {

            private final Collator collator;

            LocaleAwareComparator() {
                // Uses effectively-final currentLocale from enclosing scope
                collator = Collator.getInstance(
                    Locale.forLanguageTag(currentLocale));
            }

            @Override
            public int compare(String a, String b) {
                return collator.compare(a, b);
            }

            @Override
            public String toString() {
                return "LocaleAwareComparator[" + currentLocale + "]";
            }
        }

        LocaleAwareComparator comparator = new LocaleAwareComparator();
        Collections.sort(words, comparator);
        System.out.println("Sorted with: " + comparator);
    }
}

Inner Classes as Implementation Patterns

Inner classes appear in several important Java design patterns. The iterator pattern is the canonical use case: the iterator needs access to the collection's private internal structure (the array, the node references) and the iterator's type is tightly coupled to the collection. An inner class iterator has exactly the access it needs and is naturally scoped to the collection. The adapter pattern benefits from inner classes when the adapter needs access to the outer object's private state. A class implementing an interface whose methods need to read and write the outer class's private fields is naturally expressed as an inner class rather than a separate class with a reference to the outer object. The observer pattern's callback objects — listeners, handlers, subscribers — are often natural inner classes when they need to update the enclosing object's state in response to events. The alternative, passing a reference to the outer object explicitly, adds parameter threading through every method. The inner class makes the relationship explicit and eliminates the parameter.
Java
// ── Iterator pattern: inner class with private data access ───────────
public class IntArray implements Iterable<Integer> {

    private int[] elements;
    private int   size;

    public IntArray(int capacity) {
        elements = new int[capacity];
    }

    public void add(int value) {
        ensureCapacity();
        elements[size++] = value;
    }

    private void ensureCapacity() {
        if (size == elements.length) {
            elements = Arrays.copyOf(elements, size * 2);
        }
    }

    // ── Private inner iterator — accesses private elements[] and size ─
    private class IntIterator implements Iterator<Integer> {

        private int index = 0;

        @Override
        public boolean hasNext() {
            return index < size;      // IntArray.size — private
        }

        @Override
        public Integer next() {
            if (!hasNext()) throw new NoSuchElementException();
            return elements[index++]; // IntArray.elements — private
        }

        @Override
        public void remove() {
            if (index == 0) throw new IllegalStateException();
            // Shift elements left to fill the gap
            System.arraycopy(elements, index,
                             elements, index - 1,
                             size - index);
            size--;
            index--;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new IntIterator();
    }

    public int size() { return size; }
}

// ── Adapter pattern: inner class implementing an interface ─────────────
public class LegacyLogger {

    private final List<String> logEntries = new ArrayList<>();
    private       boolean      enabled    = true;

    public void log(String level, String message) {
        if (enabled) {
            String entry = "[" + level + "] " + message;
            logEntries.add(entry);
            System.out.println(entry);
        }
    }

    // ── Inner class adapts LegacyLogger to the SLF4J-like Logger interface
    public class Slf4jAdapter implements Logger {

        private final String name;

        public Slf4jAdapter(String name) { this.name = name; }

        @Override
        public void info(String msg) {
            log("INFO", "[" + name + "] " + msg);  // LegacyLogger.log()
        }

        @Override
        public void warn(String msg) {
            log("WARN", "[" + name + "] " + msg);
        }

        @Override
        public void error(String msg, Throwable t) {
            log("ERROR", "[" + name + "] " + msg + ": " + t.getMessage());
        }

        @Override
        public boolean isInfoEnabled() { return enabled; } // LegacyLogger.enabled
    }

    public Logger getLogger(String name) {
        return new Slf4jAdapter(name);
    }

    public List<String> getEntries() {
        return Collections.unmodifiableList(logEntries);
    }
}

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.