EMZETT.
Login

Inner Classes

In short: A class defined within another class — useful when it’s closely tied in content to the enclosing class and isn’t needed anywhere else.

In more detail: A non-static inner class automatically has access to the fields of the outer instance. For classes that don’t need such access, a static nested class (static class) is usually the cleaner choice, since it can exist independently of an outer instance.

In Depth

class Car {
    private String model = "Model X";
 
    class Engine { // non-static inner class
        void start() {
            // direct access to the outer instance's private field
            System.out.println(model + ": engine starting");
        }
    }
 
    static class Tire { // static nested class
        void inflate() {
            System.out.println("Tire being inflated"); // NO access to model possible
        }
    }
}
 
Car car = new Car();
Car.Engine engine = car.new Engine(); // requires an outer instance
engine.start();
 
Car.Tire tire = new Car.Tire(); // needs NO Car instance
tire.inflate();

The decisive difference: a non-static inner class internally holds an invisible reference to its enclosing instance and therefore can’t exist without it (car.new Engine()) — in return it has full access to its private fields, even without getters. A static nested class, by contrast, behaves practically like a completely normal standalone class that’s only “grouped” for organisation within the outer class, with no access to its instance fields. Rule of thumb: does the nested class need access to the outer instance’s state → non-static; is it self-contained and only serves logical grouping (e.g. a helper type that only makes sense within this one class) → static.

Local classes

Besides inner and static nested classes, there are also local classes — defined directly WITHIN a method, visible only within that method:

class Calculation {
    int process(List<Integer> numbers) {
        class IntermediateStep { // only exists within this method
            int square(int x) { return x * x; }
        }
        IntermediateStep step = new IntermediateStep();
        return numbers.stream().mapToInt(step::square).sum();
    }
}

Local classes are comparatively rare in modern Java code, since most of their use cases can today be solved more elegantly with lambda expressions or anonymous classes — but they remain useful when a helper class needs several methods or its own state, which a lambda can’t represent.

Practical example: iterator as an inner class

A very common use case for non-static inner classes is implementing a custom iterator for a custom data structure — the iterator needs direct access to the outer class’s internal data:

class CustomList {
    private int[] elements;
 
    class MyIterator {
        private int position = 0;
        boolean hasNext() { return position < elements.length; }
        int next() { return elements[position++]; } // access to the outer instance's elements
    }
}

See also: Classes, Anonymous Classes