Java Programming Inner Classes Tutorial Study Guide

Java Inner Classes - Complete Tutorial

Master Java inner classes: member, static nested, local, and anonymous classes—with comparison tables, real-world examples, access rules, quick reference, and best practices.

What are Inner Classes?

Inner classes (also called nested classes) are classes defined inside another class. They let you logically group types that are only used in one place, which improves encapsulation and keeps related code together.

Why Use Inner Classes?

When a helper class belongs to only one outer type, nesting it avoids polluting the package and makes the relationship obvious.

Without inner class — less organized
class LinkedList {
    Node head;
}

class Node {  // Only used by LinkedList, but declared at top level
    int data;
    Node next;
}
With inner class — better organization
class LinkedList {
    class Node {  // Node lives inside LinkedList where it belongs
        int data;
        Node next;
    }
    Node head;
}

Key idea

Inner classes express has-a / part-of relationships: a Node is part of a LinkedList, not a standalone public type in the package.

Types of Inner Classes

Java supports four kinds of nested classes. Choose based on whether you need an outer instance, static context, method scope, or a one-off implementation.

Type Static? Can access outer instance? When to use
Member Inner Class No Yes When the nested class needs access to outer instance fields and methods
Static Nested Class Yes No (only static outer members) When the nested class does not need an outer instance
Local Inner Class No Yes Inside a method or block, for limited scope
Anonymous Inner Class No Yes One-time implementation of an interface or extension of a class

Member Inner Class

A non-static class defined at the member level inside another class. Each inner object is tied to an outer object and can access all outer members, including private ones.

Basic Example

OuterClass and InnerClass
public class OuterClass {
    private String outerField = "Outer Field";
    private static String staticField = "Static Field";

    // Member Inner Class
    class InnerClass {
        private String innerField = "Inner Field";

        public void display() {
            // Can access all members of outer class (including private)
            System.out.println("Accessing: " + outerField);
            System.out.println("Accessing: " + staticField);
            System.out.println("Accessing: " + innerField);
        }
    }

    public void createInner() {
        InnerClass inner = new InnerClass();
        inner.display();
    }

    public static void main(String[] args) {
        // Creating member inner class requires outer instance
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.display();
    }
}
Syntax: Use outer.new InnerClass() — not new OuterClass.InnerClass() alone for member inner classes.

Complete Example: Employee Management System

Company and Employee
import java.util.*;

public class Company {
    private String companyName;
    private List<Employee> employees = new ArrayList<>();

    public Company(String companyName) {
        this.companyName = companyName;
    }

    // Member Inner Class
    class Employee {
        private int id;
        private String name;
        private double salary;

        public Employee(int id, String name, double salary) {
            this.id = id;
            this.name = name;
            this.salary = salary;
        }

        public void displayInfo() {
            System.out.println("Company: " + companyName);
            System.out.println("Employee ID: " + id);
            System.out.println("Employee Name: " + name);
            System.out.println("Salary: $" + salary);
        }

        public void giveRaise(double percentage) {
            salary += salary * (percentage / 100);
            System.out.println(name + " received " + percentage + "% raise");
        }

        public void addToCompany() {
            employees.add(this);
        }
    }

    public void listAllEmployees() {
        System.out.println("\n--- Employees of " + companyName + " ---");
        for (Employee emp : employees) {
            emp.displayInfo();
            System.out.println("---");
        }
    }

    public static void main(String[] args) {
        Company techCorp = new Company("TechCorp");

        Company.Employee emp1 = techCorp.new Employee(101, "Alice", 75000);
        Company.Employee emp2 = techCorp.new Employee(102, "Bob", 68000);

        emp1.addToCompany();
        emp2.addToCompany();

        emp1.giveRaise(10);
        techCorp.listAllEmployees();
    }
}

Nested Inner Classes

Inner classes can be nested multiple levels deep. Each level can access members of its enclosing classes.

University, Department, and Student
public class University {
    private String name = "State University";

    class Department {
        private String deptName;

        Department(String deptName) {
            this.deptName = deptName;
        }

        class Student {
            private String studentName;

            Student(String studentName) {
                this.studentName = studentName;
            }

            void display() {
                System.out.println("University: " + name);
                System.out.println("Department: " + deptName);
                System.out.println("Student: " + studentName);
            }
        }

        Student createStudent(String name) {
            return new Student(name);
        }
    }

    public static void main(String[] args) {
        University uni = new University();
        University.Department csDept = uni.new Department("Computer Science");
        University.Department.Student alice = csDept.new Student("Alice");
        alice.display();
    }
}

Static Nested Class

A static class nested inside another class. It behaves like a top-level class but is packaged inside the outer type for organization. It cannot access non-static outer members directly.

Basic Example

Static nested class basics
public class OuterClass {
    private String instanceField = "Instance";
    private static String staticField = "Static";

    static class StaticNestedClass {
        private String nestedField = "Nested Field";

        public void display() {
            // Cannot access instance members of outer class
            // System.out.println(instanceField); // ERROR!

            System.out.println("Accessing: " + staticField);
            System.out.println("Accessing: " + nestedField);
        }

        public static void staticMethod() {
            System.out.println("Static method in nested class");
        }
    }

    public static void main(String[] args) {
        OuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();
        nested.display();
        OuterClass.StaticNestedClass.staticMethod();
    }
}

Practical Example: Calculator with Operation Constants

Calculator with nested helpers
public class Calculator {
    private double result = 0;

    public static class MathConstants {
        public static final double PI = 3.14159;
        public static final double E = 2.71828;
        public static final double GOLDEN_RATIO = 1.61803;

        public static void displayConstants() {
            System.out.println("PI: " + PI);
            System.out.println("E: " + E);
            System.out.println("Golden Ratio: " + GOLDEN_RATIO);
        }
    }

    public static class UnitConverter {
        public static double kgToLbs(double kg) {
            return kg * 2.20462;
        }

        public static double lbsToKg(double lbs) {
            return lbs / 2.20462;
        }

        public static double celsiusToFahrenheit(double celsius) {
            return (celsius * 9 / 5) + 32;
        }

        public static double fahrenheitToCelsius(double fahrenheit) {
            return (fahrenheit - 32) * 5 / 9;
        }
    }

    public static class Geometry {
        public static double circleArea(double radius) {
            return MathConstants.PI * radius * radius;
        }

        public static double rectangleArea(double length, double width) {
            return length * width;
        }

        public static double triangleArea(double base, double height) {
            return 0.5 * base * height;
        }
    }

    public static void main(String[] args) {
        System.out.println("Math Constants:");
        Calculator.MathConstants.displayConstants();

        System.out.println("\nUnit Conversions:");
        System.out.println("70 kg = " + Calculator.UnitConverter.kgToLbs(70) + " lbs");
        System.out.println("25°C = " + Calculator.UnitConverter.celsiusToFahrenheit(25) + "°F");

        System.out.println("\nGeometry:");
        System.out.println("Circle area (r=5): " + Calculator.Geometry.circleArea(5));
        System.out.println("Rectangle area (4x6): " + Calculator.Geometry.rectangleArea(4, 6));
    }
}

Builder Pattern with Static Nested Class

User and Builder
public class User {
    private final String username;
    private final String email;
    private final int age;
    private final String phone;
    private final String address;

    private User(Builder builder) {
        this.username = builder.username;
        this.email = builder.email;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public static class Builder {
        private final String username;
        private final String email;
        private int age = 0;
        private String phone = "";
        private String address = "";

        public Builder(String username, String email) {
            this.username = username;
            this.email = email;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public Builder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }

    @Override
    public String toString() {
        return String.format(
            "User{username='%s', email='%s', age=%d, phone='%s', address='%s'}",
            username, email, age, phone, address);
    }

    public static void main(String[] args) {
        User user1 = new User.Builder("john_doe", "john@example.com")
                .age(25)
                .phone("555-1234")
                .address("123 Main St")
                .build();

        User user2 = new User.Builder("jane_smith", "jane@example.com")
                .age(30)
                .build();

        System.out.println(user1);
        System.out.println(user2);
    }
}
Prefer static nested when the nested type does not need outer instance state — avoids holding an implicit reference to the outer object (memory leak risk in long-lived inner instances).

Local Inner Class

A class defined inside a method, constructor, or block. It is visible only within that block and is often used when the helper type is needed in one place only.

Local class inside a method
public class OuterClass {
    private String outerField = "Outer";

    public void someMethod() {
        final String localVar = "Local Variable";
        String effectivelyFinal = "Effectively Final";

        class LocalClass {
            private String localInnerField = "Local Inner";

            public void display() {
                System.out.println("Outer field: " + outerField);
                System.out.println("Local variable: " + localVar);
                System.out.println("Effectively final: " + effectivelyFinal);
                System.out.println("Local inner field: " + localInnerField);
            }

            public void modifyOuter() {
                outerField = "Modified by local class";
            }
        }

        LocalClass local = new LocalClass();
        local.display();
        local.modifyOuter();
        System.out.println("After modification: " + outerField);
    }

    public static void main(String[] args) {
        new OuterClass().someMethod();
    }
}

Effectively final

Local and anonymous classes can read local variables only if they are final or effectively final (never reassigned after initialization).

Anonymous Inner Class

An anonymous inner class has no name. You define and instantiate it in one expression — ideal for one-time implementations of an interface or extension of a class.

Interface, abstract class, and Thread
public class AnonymousDemo {

    interface Greeting {
        void greet();
    }

    abstract class Animal {
        abstract void makeSound();
    }

    public static void main(String[] args) {
        // Anonymous class implementing an interface
        Greeting greeting = new Greeting() {
            @Override
            public void greet() {
                System.out.println("Hello from anonymous class!");
            }
        };
        greeting.greet();

        // Anonymous class extending an abstract class
        Animal dog = new Animal() {
            @Override
            void makeSound() {
                System.out.println("Woof! Woof!");
            }
        };
        dog.makeSound();

        // Anonymous class extending Thread
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("Running in anonymous thread");
            }
        };
        thread.start();

        // Anonymous class implementing Runnable
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable in anonymous class");
            }
        };
        new Thread(task).start();
    }
}
Java 8+: For functional interfaces (one abstract method), prefer () -> { } lambdas instead of anonymous classes when the logic is short.

Accessing Outer Class Members

From inside a member, local, or anonymous inner class, you can access outer instance members directly. When names clash between inner and outer, use OuterClass.this to refer to the enclosing instance.

Outer.this syntax
public class Outer {
    private int value = 10;
    private String name = "Outer";

    class Inner {
        private int value = 20;

        void show() {
            System.out.println("Inner value: " + value);           // 20
            System.out.println("Outer value: " + Outer.this.value); // 10
            System.out.println("Outer name: " + Outer.this.name);
        }

        Outer getOuter() {
            return Outer.this;
        }
    }

    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.show();
        System.out.println("Same outer? " + (inner.getOuter() == outer));
    }
}

Static nested classes cannot use Outer.this because they have no enclosing instance. They may only access static members of the outer class.

Practical Examples

The examples above cover real-world patterns you will see in production code:

  • Member inner classCompany.Employee ties employees to a company instance; nested University.Department.Student models multi-level containment.
  • Static nested classCalculator groups constants and utilities; User.Builder implements the builder pattern without exposing a public constructor.
  • Local inner class — helper logic scoped to a single method in OuterClass.someMethod().
  • Anonymous inner class — quick callbacks for interfaces, abstract classes, and Thread / Runnable.

Together, these show when to nest for encapsulation, when to use static for independence, and when anonymous or lambda syntax is enough for a one-off behavior.

Common Use Cases

  • Event listeners — button clicks, mouse events (ActionListener, MouseListener)
  • Adapters — provide default empty implementations; override only what you need
  • Builders — fluent object construction via static nested Builder classes
  • Data structuresNode inside LinkedList, iterators inside collections
  • Callbacks — pass behavior into APIs (Runnable, comparators, custom handlers)

Best Practices and Pitfalls

DO

  • Use static nested classes when the nested type does not need outer instance state
  • Keep inner classes small and focused on one responsibility
  • Use inner classes to hide implementation details from the rest of the package
  • Prefer lambda expressions (Java 8+) for simple functional-interface callbacks
  • Use Outer.this when inner and outer fields share the same name

DON'T

  • Don't use non-static inner classes in long-lived objects if they capture the outer instance unnecessarily (memory leak risk)
  • Don't nest too deeply — more than two levels hurts readability
  • Don't use anonymous classes when a named class or lambda is clearer
  • Don't try to modify local variables from inner classes — they must be effectively final
  • Don't confuse Outer.Inner (static nested) with outer.new Inner() (member inner)
Lambdas vs anonymous classes: For interfaces like Runnable, Comparator<T>, or ActionListener, use () -> { } when you only need a short implementation. Keep anonymous classes when you override multiple methods or extend a class with state.
Lambda replaces simple anonymous Runnable
// Anonymous inner class (older style)
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

// Lambda (Java 8+) — preferred when short
Runnable r2 = () -> System.out.println("Hello");

Quick Reference Card

Inner classes cheat sheet
// 1. MEMBER INNER CLASS
class Outer {
    class Inner {
        void method() {
            Outer.this.outerMethod();
        }
    }
}
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();

// 2. STATIC NESTED CLASS
class Outer {
    static class Nested { }
}
Outer.Nested nested = new Outer.Nested();

// 3. LOCAL INNER CLASS
class Outer {
    void method() {
        class Local { }
        Local local = new Local();
    }
}

// 4. ANONYMOUS INNER CLASS
Runnable r = new Runnable() {
    @Override
    public void run() { }
};

// 5. ACCESSING OUTER THIS
class Outer {
    class Inner {
        Outer getOuter() {
            return Outer.this;
        }
    }
}

// 6. MODIFYING LOCAL VARIABLES (workaround)
class Outer {
    void method() {
        int[] counter = {0};
        Runnable r = new Runnable() {
            public void run() {
                counter[0]++;
            }
        };
    }
}

Key Takeaways

  • Four types of inner classes: Member, Static Nested, Local, Anonymous
  • Member inner classes require an outer instance and can access all outer members
  • Static nested classes do not need an outer instance and can only access static outer members
  • Local inner classes are defined inside methods and have limited scope
  • Anonymous inner classes provide quick, one-time implementations
  • Use static nested classes to avoid memory leaks and for better performance when no outer state is needed
  • Inner classes increase encapsulation by hiding implementation details
  • Accessing outer members from inner classes uses OuterClass.this syntax
  • Local and anonymous classes can only access effectively final local variables
  • Consider lambda expressions instead of anonymous classes in Java 8+

Summary

Choose the nested type by whether you need outer instance access: member/local/anonymous when yes, static nested when no. Use builders and data-structure nodes as static or member nested classes, and prefer lambdas for simple one-method callbacks.