1. Introduction to Inner Classes
What are Inner Classes?
Inner classes are classes defined within another class. They provide a way to logically group classes that are only used in one place and increase encapsulation.
Why Use Inner Classes?
- Logical grouping of classes
- Increased encapsulation
- More readable and maintainable code
- Access to private members of outer class
- Callback mechanism implementation
Types of Inner Classes:
- Non-static Nested Classes (Inner Classes)
- Static Nested Classes
- Local Classes (inside methods/blocks)
- Anonymous Classes (without name)
2. Non-Static Nested Classes (Inner Classes)
// Outer class class OuterClass { private String outerField = "Outer field"; private static String staticOuterField = "Static outer field"; // Non-static inner class class InnerClass { private String innerField = "Inner field"; public void display() { System.out.println("Inner class method:"); System.out.println("Accessing outer field: " + outerField); // Direct access System.out.println("Accessing static outer field: " + staticOuterField); System.out.println("Accessing inner field: " + innerField); } public void accessOuterMethod() { outerMethod(); // Can access outer class methods directly } } public void outerMethod() { System.out.println("Outer class method called"); } public void createInner() { InnerClass inner = new InnerClass(); inner.display(); } } public class NonStaticInnerClassExample { public static void main(String[] args) { System.out.println("=== Non-Static Inner Class Example ==="); // Create outer class instance OuterClass outer = new OuterClass(); // Create inner class instance using outer instance OuterClass.InnerClass inner = outer.new InnerClass(); inner.display(); inner.accessOuterMethod(); System.out.println("\n--- Creating inner from outer method ---"); outer.createInner(); } } 3. Static Nested Classes
class OuterClassWithStatic { private String instanceField = "Instance field"; private static String staticField = "Static field"; // Static nested class static class StaticNestedClass { private String nestedField = "Nested field"; public void display() { System.out.println("Static nested class method:"); // System.out.println("Cannot access instance field: " + instanceField); // ERROR! System.out.println("Accessing static field: " + staticField); System.out.println("Accessing nested field: " + nestedField); } public static void staticMethod() { System.out.println("Static method in static nested class"); } } // Non-static inner class for comparison class RegularInnerClass { public void display() { System.out.println("Regular inner class can access: " + instanceField); } } } public class StaticNestedClassExample { public static void main(String[] args) { System.out.println("=== Static Nested Class Example ==="); // No need for outer class instance OuterClassWithStatic.StaticNestedClass staticNested = new OuterClassWithStatic.StaticNestedClass(); staticNested.display(); // Call static method directly OuterClassWithStatic.StaticNestedClass.staticMethod(); System.out.println("\n--- Comparison with Regular Inner Class ---"); OuterClassWithStatic outer = new OuterClassWithStatic(); OuterClassWithStatic.RegularInnerClass regularInner = outer.new RegularInnerClass(); regularInner.display(); } } 4. Local Classes (Method-Local Inner Classes)
class LocalClassExample { private String outerField = "Outer field"; public void methodWithLocalClass() { final String localVariable = "Local variable"; // Must be final or effectively final // Local class defined inside method class LocalClass { private String localClassField = "Local class field"; public void display() { System.out.println("Local class method:"); System.out.println("Accessing outer field: " + outerField); System.out.println("Accessing local variable: " + localVariable); // Can access final locals System.out.println("Accessing local class field: " + localClassField); } } // Create and use local class LocalClass local = new LocalClass(); local.display(); } public void methodWithParameter(final int param) { // Local class with method parameter access class ParameterLocalClass { public void useParameter() { System.out.println("Using method parameter: " + param); System.out.println("Outer field: " + outerField); } } new ParameterLocalClass().useParameter(); } public Runnable createCounter() { int count = 0; // Effectively final // Local class implementing interface class Counter implements Runnable { @Override public void run() { // count++; // Would make count not effectively final - ERROR! System.out.println("Count: " + count); System.out.println("Outer field: " + outerField); } } return new Counter(); } } public class LocalClassExamples { public static void main(String[] args) { System.out.println("=== Local Class Examples ==="); LocalClassExample example = new LocalClassExample(); System.out.println("--- Method with Local Class ---"); example.methodWithLocalClass(); System.out.println("\n--- Method with Parameter ---"); example.methodWithParameter(42); System.out.println("\n--- Local Class as Runnable ---"); Runnable counter = example.createCounter(); counter.run(); } } 5. Anonymous Classes
// Interface for anonymous class example interface Greeting { void greet(); void greetSomeone(String name); } // Abstract class for anonymous class example abstract class Animal { private String name; public Animal(String name) { this.name = name; } public String getName() { return name; } public abstract void makeSound(); public abstract void sleep(); } public class AnonymousClassExamples { public static void main(String[] args) { System.out.println("=== Anonymous Class Examples ==="); // 1. Anonymous class implementing interface Greeting greeting = new Greeting() { private String defaultName = "World"; @Override public void greet() { System.out.println("Hello, " + defaultName + "!"); } @Override public void greetSomeone(String name) { System.out.println("Hello, " + name + "!"); } // Additional method (only accessible right after creation) public void additionalMethod() { System.out.println("This is an additional method"); } }; greeting.greet(); greeting.greetSomeone("Java"); // greeting.additionalMethod(); // Cannot call - not in interface // 2. Anonymous class extending abstract class Animal dog = new Animal("Buddy") { @Override public void makeSound() { System.out.println(getName() + " says: Woof! Woof!"); } @Override public void sleep() { System.out.println(getName() + " is sleeping... Zzz"); } public void fetch() { System.out.println(getName() + " is fetching the ball!"); } }; dog.makeSound(); dog.sleep(); // dog.fetch(); // Cannot call - not in Animal class // 3. Anonymous class as method parameter System.out.println("\n--- Anonymous Class as Parameter ---"); processAnimal(new Animal("Mittens") { @Override public void makeSound() { System.out.println(getName() + " says: Meow!"); } @Override public void sleep() { System.out.println(getName() + " is purring and sleeping..."); } }); // 4. Anonymous class with Runnable (common use case) System.out.println("\n--- Anonymous Runnable ---"); Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 3; i++) { System.out.println("Anonymous thread running: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 5. Lambda vs Anonymous class (Java 8+) System.out.println("\n--- Lambda vs Anonymous Class ---"); // Anonymous class way Runnable anonymousRunnable = new Runnable() { @Override public void run() { System.out.println("Running from anonymous class"); } }; // Lambda way (equivalent) Runnable lambdaRunnable = () -> System.out.println("Running from lambda"); anonymousRunnable.run(); lambdaRunnable.run(); } public static void processAnimal(Animal animal) { System.out.println("Processing " + animal.getName()); animal.makeSound(); animal.sleep(); } } 6. Complete Real-World Examples
Example 1: Data Structure with Iterator
import java.util.*; // Custom collection with inner class iterator class CustomList<T> implements Iterable<T> { private Node<T> head; private int size; // Static nested class for Node private static class Node<T> { T data; Node<T> next; Node(T data) { this.data = data; this.next = null; } } public void add(T data) { Node<T> newNode = new Node<>(data); if (head == null) { head = newNode; } else { Node<T> current = head; while (current.next != null) { current = current.next; } current.next = newNode; } size++; } public int size() { return size; } // Non-static inner class as iterator @Override public Iterator<T> iterator() { return new CustomIterator(); } private class CustomIterator implements Iterator<T> { private Node<T> current = head; @Override public boolean hasNext() { return current != null; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } T data = current.data; current = current.next; return data; } @Override public void remove() { throw new UnsupportedOperationException("Remove not supported"); } } // Local class example in method public void processWithLocalClass() { class ListProcessor { public void printList() { System.out.print("List contents: "); for (T item : CustomList.this) { System.out.print(item + " "); } System.out.println(); } public int getSize() { return size; // Access outer class field } } ListProcessor processor = new ListProcessor(); processor.printList(); System.out.println("List size: " + processor.getSize()); } } public class DataStructureExample { public static void main(String[] args) { System.out.println("=== Custom List with Inner Class Iterator ==="); CustomList<String> list = new CustomList<>(); list.add("Java"); list.add("Python"); list.add("C++"); list.add("JavaScript"); System.out.println("Using iterator:"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(" - " + iterator.next()); } System.out.println("\nUsing for-each loop:"); for (String language : list) { System.out.println(" - " + language); } System.out.println("\nUsing local class method:"); list.processWithLocalClass(); } } Example 2: GUI Event Handling
import java.util.*; import java.util.function.Consumer; // Simple event system using inner classes class Button { private String label; private List<ClickListener> clickListeners = new ArrayList<>(); private List<Consumer<String>> actionListeners = new ArrayList<>(); public Button(String label) { this.label = label; } // Static nested interface public static interface ClickListener { void onClick(Button source); } // Non-static inner class for default click listener class DefaultClickListener implements ClickListener { @Override public void onClick(Button source) { System.out.println("Default click handler: " + label + " clicked!"); } } public void addClickListener(ClickListener listener) { clickListeners.add(listener); } public void addActionListener(Consumer<String> listener) { actionListeners.add(listener); } public void click() { System.out.println("--- Button '" + label + "' clicked ---"); // Notify click listeners for (ClickListener listener : clickListeners) { listener.onClick(this); } // Notify action listeners for (Consumer<String> listener : actionListeners) { listener.accept(label); } } public String getLabel() { return label; } } public class GUIEventExample { public static void main(String[] args) { System.out.println("=== GUI Event Handling with Inner Classes ==="); Button submitButton = new Button("Submit"); Button cancelButton = new Button("Cancel"); // 1. Using non-static inner class Button.ClickListener defaultHandler = submitButton.new DefaultClickListener(); submitButton.addClickListener(defaultHandler); // 2. Using anonymous class submitButton.addClickListener(new Button.ClickListener() { @Override public void onClick(Button source) { System.out.println("Anonymous handler: Processing form submission..."); System.out.println("Button label: " + source.getLabel()); } }); // 3. Using lambda (Java 8+) submitButton.addActionListener(label -> { System.out.println("Lambda action: Form submitted via " + label); }); // 4. Local class for complex handler class ComplexClickHandler implements Button.ClickListener { private int clickCount = 0; @Override public void onClick(Button source) { clickCount++; System.out.println("Complex handler: Click count = " + clickCount); if (clickCount >= 3) { System.out.println("Triple click detected!"); } } } cancelButton.addClickListener(new ComplexClickHandler()); // 5. Simulate clicks System.out.println("\n--- Simulating Clicks ---"); submitButton.click(); cancelButton.click(); cancelButton.click(); cancelButton.click(); System.out.println("\n--- Multiple Anonymous Handlers ---"); Button multiHandlerButton = new Button("Multi-Handler"); // Multiple anonymous handlers multiHandlerButton.addClickListener(new Button.ClickListener() { @Override public void onClick(Button source) { System.out.println("Handler 1: Basic logging"); } }); multiHandlerButton.addClickListener(new Button.ClickListener() { @Override public void onClick(Button source) { System.out.println("Handler 2: Analytics tracking"); } }); multiHandlerButton.addClickListener(new Button.ClickListener() { @Override public void onClick(Button source) { System.out.println("Handler 3: UI update"); } }); multiHandlerButton.click(); } } Example 3: Builder Pattern with Static Nested Class
// Computer class with Builder pattern using static nested class class Computer { private final String cpu; private final String ram; private final String storage; private final String graphicsCard; private final boolean hasBluetooth; private final boolean hasWifi; // Private constructor private Computer(Builder builder) { this.cpu = builder.cpu; this.ram = builder.ram; this.storage = builder.storage; this.graphicsCard = builder.graphicsCard; this.hasBluetooth = builder.hasBluetooth; this.hasWifi = builder.hasWifi; } // Static nested Builder class public static class Builder { // Required parameters private final String cpu; private final String ram; // Optional parameters - initialized to default values private String storage = "256GB SSD"; private String graphicsCard = "Integrated"; private boolean hasBluetooth = false; private boolean hasWifi = false; public Builder(String cpu, String ram) { this.cpu = cpu; this.ram = ram; } public Builder storage(String storage) { this.storage = storage; return this; } public Builder graphicsCard(String graphicsCard) { this.graphicsCard = graphicsCard; return this; } public Builder bluetooth(boolean hasBluetooth) { this.hasBluetooth = hasBluetooth; return this; } public Builder wifi(boolean hasWifi) { this.hasWifi = hasWifi; return this; } public Computer build() { return new Computer(this); } } // Getters public String getCpu() { return cpu; } public String getRam() { return ram; } public String getStorage() { return storage; } public String getGraphicsCard() { return graphicsCard; } public boolean hasBluetooth() { return hasBluetooth; } public boolean hasWifi() { return hasWifi; } @Override public String toString() { return String.format("Computer[CPU=%s, RAM=%s, Storage=%s, Graphics=%s, Bluetooth=%s, WiFi=%s]", cpu, ram, storage, graphicsCard, hasBluetooth, hasWifi); } } public class BuilderPatternExample { public static void main(String[] args) { System.out.println("=== Builder Pattern with Static Nested Class ==="); // Build computers using the builder Computer gamingPC = new Computer.Builder("Intel i9", "32GB") .storage("1TB NVMe SSD") .graphicsCard("NVIDIA RTX 4080") .bluetooth(true) .wifi(true) .build(); Computer officePC = new Computer.Builder("Intel i5", "16GB") .storage("512GB SSD") .wifi(true) .build(); Computer basicPC = new Computer.Builder("Intel i3", "8GB") .build(); System.out.println("Gaming PC: " + gamingPC); System.out.println("Office PC: " + officePC); System.out.println("Basic PC: " + basicPC); // Demonstrate method chaining System.out.println("\n--- Builder Method Chaining ---"); Computer customPC = new Computer.Builder("AMD Ryzen 7", "16GB") .storage("2TB HDD + 512GB SSD") .graphicsCard("AMD Radeon RX 6700") .bluetooth(true) .wifi(true) .build(); System.out.println("Custom PC: " + customPC); } } Example 4: Bank Account System
import java.util.*; // Bank account system with various inner classes class BankAccount { private final String accountNumber; private String accountHolder; private double balance; private final List<Transaction> transactionHistory; public BankAccount(String accountNumber, String accountHolder, double initialBalance) { this.accountNumber = accountNumber; this.accountHolder = accountHolder; this.balance = initialBalance; this.transactionHistory = new ArrayList<>(); this.transactionHistory.add(new Transaction("OPENING", initialBalance, initialBalance)); } // Static nested class for Transaction public static class Transaction { private final String type; private final double amount; private final double balanceAfter; private final Date timestamp; public Transaction(String type, double amount, double balanceAfter) { this.type = type; this.amount = amount; this.balanceAfter = balanceAfter; this.timestamp = new Date(); } @Override public String toString() { return String.format("[%s] %s: $%.2f -> Balance: $%.2f", timestamp, type, amount, balanceAfter); } } // Non-static inner class for Account operations public class AccountManager { public void deposit(double amount) { if (amount > 0) { balance += amount; transactionHistory.add(new Transaction("DEPOSIT", amount, balance)); System.out.println("Deposited: $" + amount); } } public boolean withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; transactionHistory.add(new Transaction("WITHDRAW", -amount, balance)); System.out.println("Withdrawn: $" + amount); return true; } else { System.out.println("Insufficient funds or invalid amount"); return false; } } public void transfer(BankAccount target, double amount) { if (withdraw(amount)) { target.getAccountManager().deposit(amount); transactionHistory.add(new Transaction("TRANSFER_OUT", -amount, balance)); target.transactionHistory.add(new Transaction("TRANSFER_IN", amount, target.balance)); System.out.println("Transferred $" + amount + " to " + target.accountHolder); } } } // Local class in method public void generateStatement() { class StatementGenerator { private final Date generatedDate; public StatementGenerator() { this.generatedDate = new Date(); } public void generate() { System.out.println("\n=== BANK STATEMENT ==="); System.out.println("Account: " + accountNumber); System.out.println("Holder: " + accountHolder); System.out.println("Generated: " + generatedDate); System.out.println("Current Balance: $" + balance); System.out.println("\nTransaction History:"); for (Transaction transaction : transactionHistory) { System.out.println(" " + transaction); } System.out.println("=== END STATEMENT ===\n"); } } StatementGenerator generator = new StatementGenerator(); generator.generate(); } // Anonymous class for notification public interface BalanceAlert { void alert(double currentBalance, double threshold); } public void setLowBalanceAlert(double threshold) { BalanceAlert alert = new BalanceAlert() { private final double alertThreshold = threshold; @Override public void alert(double currentBalance, double threshold) { if (currentBalance < alertThreshold) { System.out.println("🚨 LOW BALANCE ALERT: $" + currentBalance + " is below threshold: $" + alertThreshold); } } }; // Simulate checking balance alert.alert(balance, threshold); } // Get account manager public AccountManager getAccountManager() { return new AccountManager(); } // Getters public String getAccountNumber() { return accountNumber; } public String getAccountHolder() { return accountHolder; } public double getBalance() { return balance; } } public class BankAccountExample { public static void main(String[] args) { System.out.println("=== Bank Account System with Inner Classes ==="); // Create accounts BankAccount aliceAccount = new BankAccount("ACC001", "Alice Johnson", 1000); BankAccount bobAccount = new BankAccount("ACC002", "Bob Smith", 500); // Get account managers BankAccount.AccountManager aliceManager = aliceAccount.getAccountManager(); BankAccount.AccountManager bobManager = bobAccount.getAccountManager(); System.out.println("--- Initial State ---"); System.out.println("Alice balance: $" + aliceAccount.getBalance()); System.out.println("Bob balance: $" + bobAccount.getBalance()); System.out.println("\n--- Performing Transactions ---"); aliceManager.deposit(200); aliceManager.withdraw(150); aliceManager.transfer(bobAccount, 300); bobManager.withdraw(100); System.out.println("\n--- Final State ---"); System.out.println("Alice balance: $" + aliceAccount.getBalance()); System.out.println("Bob balance: $" + bobAccount.getBalance()); System.out.println("\n--- Generating Statements ---"); aliceAccount.generateStatement(); bobAccount.generateStatement(); System.out.println("--- Balance Alerts ---"); aliceAccount.setLowBalanceAlert(1000); bobAccount.setLowBalanceAlert(200); // Demonstrate transaction history access System.out.println("\n--- Transaction Types Used ---"); // This would require making Transaction class public or adding getters } } 7. Advanced Concepts and Best Practices
import java.util.*; import java.util.function.Predicate; public class AdvancedInnerClassConcepts { // Outer class static class University { private String name; private List<Department> departments; public University(String name) { this.name = name; this.departments = new ArrayList<>(); } // Static nested class static class Department { private String name; private String code; public Department(String name, String code) { this.name = name; this.code = code; } @Override public String toString() { return code + ": " + name; } } // Non-static inner class class DepartmentManager { public void addDepartment(String name, String code) { departments.add(new Department(name, code)); } public void listDepartments() { System.out.println("Departments in " + University.this.name + ":"); for (Department dept : departments) { System.out.println(" - " + dept); } } // Accessing outer class "this" explicitly public void showUniversityInfo() { System.out.println("Managing departments for: " + University.this.name); } } // Method with local class implementing interface public void analyzeDepartments() { class DepartmentAnalyzer implements Predicate<Department> { private int minNameLength; public DepartmentAnalyzer(int minNameLength) { this.minNameLength = minNameLength; } @Override public boolean test(Department dept) { return dept.name.length() >= minNameLength; } public void analyze() { long count = departments.stream().filter(this).count(); System.out.println(count + " departments have names with " + minNameLength + "+ characters"); } } DepartmentAnalyzer analyzer = new DepartmentAnalyzer(10); analyzer.analyze(); } // Method returning anonymous class public Comparator<Department> getDepartmentComparator() { return new Comparator<Department>() { @Override public int compare(Department d1, Department d2) { int codeCompare = d1.code.compareTo(d2.code); if (codeCompare != 0) return codeCompare; return d1.name.compareTo(d2.name); } }; } } public static void main(String[] args) { System.out.println("=== Advanced Inner Class Concepts ==="); University university = new University("Tech University"); University.DepartmentManager manager = university.new DepartmentManager(); // Add departments manager.addDepartment("Computer Science", "CS"); manager.addDepartment("Electrical Engineering", "EE"); manager.addDepartment("Mechanical Engineering", "ME"); manager.addDepartment("Mathematics", "MATH"); manager.addDepartment("Physics", "PHYS"); manager.listDepartments(); manager.showUniversityInfo(); System.out.println("\n--- Department Analysis ---"); university.analyzeDepartments(); System.out.println("\n--- Sorting Departments ---"); List<University.Department> departments = new ArrayList<>(); departments.add(new University.Department("Computer Science", "CS")); departments.add(new University.Department("Electrical Engineering", "EE")); departments.add(new University.Department("Mathematics", "MATH")); Collections.sort(departments, university.getDepartmentComparator()); System.out.println("Sorted departments:"); departments.forEach(dept -> System.out.println(" " + dept)); // Demonstrating access levels System.out.println("\n--- Access Level Demonstration ---"); demonstrateAccessLevels(); } public static void demonstrateAccessLevels() { class Outer { private String privateField = "private"; String packageField = "package"; protected String protectedField = "protected"; public String publicField = "public"; class Inner { public void accessAll() { System.out.println("Inner class can access:"); System.out.println(" - " + privateField); System.out.println(" - " + packageField); System.out.println(" - " + protectedField); System.out.println(" - " + publicField); } } } Outer outer = new Outer(); Outer.Inner inner = outer.new Inner(); inner.accessAll(); } } // Best practices and common patterns class InnerClassBestPractices { // 1. Use static nested classes when you don't need access to instance members public static class UtilityClass { public static int add(int a, int b) { return a + b; } } // 2. Use non-static inner classes for tight coupling with outer class public class TightlyCoupledHelper { public void help() { // Can access outer class instance members System.out.println("Helping with instance-specific tasks"); } } // 3. Factory method pattern with inner classes public interface Product { void use(); } private class ConcreteProduct implements Product { @Override public void use() { System.out.println("Using concrete product"); } } public Product createProduct() { return new ConcreteProduct(); } // 4. Callback pattern with inner classes public interface OperationComplete { void onComplete(String result); } public void performAsyncOperation(OperationComplete callback) { new Thread(() -> { try { Thread.sleep(1000); // Simulate work callback.onComplete("Operation completed successfully"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } class BestPracticesDemo { public static void main(String[] args) { System.out.println("=== Best Practices Demonstration ==="); InnerClassBestPractices practices = new InnerClassBestPractices(); // Using static nested class System.out.println("Static utility: " + InnerClassBestPractices.UtilityClass.add(5, 3)); // Using non-static inner class InnerClassBestPractices.TightlyCoupledHelper helper = practices.new TightlyCoupledHelper(); helper.help(); // Using factory method InnerClassBestPractices.Product product = practices.createProduct(); product.use(); // Using callback with anonymous class System.out.println("\n--- Async Operation with Callback ---"); practices.performAsyncOperation(new InnerClassBestPractices.OperationComplete() { @Override public void onComplete(String result) { System.out.println("Callback received: " + result); } }); try { Thread.sleep(1500); // Wait for async operation } catch (InterruptedException e) { e.printStackTrace(); } } } 8. Conclusion
Key Takeaways:
- Non-static Inner Classes:
- Have access to outer class instance members
- Require an instance of outer class to create
- Can access private members of outer class
- Static Nested Classes:
- Don't have access to outer class instance members
- Can be created without outer class instance
- Useful for grouping related classes
- Local Classes:
- Defined within methods or blocks
- Can access final or effectively final local variables
- Useful for one-time use implementations
- Anonymous Classes:
- No name, defined and instantiated in one expression
- Useful for quick interface/abstract class implementations
- Largely replaced by lambdas in Java 8+
When to Use Which:
- ✅ Static Nested Classes: When the nested class doesn't need outer instance access
- ✅ Non-static Inner Classes: When you need access to outer class instance members
- ✅ Local Classes: For method-specific implementations
- ✅ Anonymous Classes: For one-time interface implementations
Best Practices:
- Use the most restrictive access level possible
- Prefer static nested classes unless you need outer instance access
- Keep inner classes small and focused
- Use meaningful names for inner classes
- Consider using interfaces with lambdas instead of anonymous classes
Common Pitfalls:
- ❌ Creating memory leaks by holding references to outer classes
- ❌ Overusing inner classes when separate classes would be better
- ❌ Making inner classes public when they should be private
- ❌ Forgetting that local classes can only access final variables
Performance Considerations:
- Static nested classes have better performance
- Non-static inner classes have hidden reference to outer class
- Anonymous classes create additional .class files
Final Thoughts:
Inner classes are a powerful feature that, when used appropriately, can lead to more organized, encapsulated, and maintainable code. They're particularly useful for:
- Implementing iterators
- Building event handling systems
- Creating builder patterns
- Organizing related utility classes
Master inner classes to write more expressive and well-organized Java code!