Java Inner Classes - Complete Tutorial
Master Java Inner Classes: Learn nested classes, inner classes, static nested classes, local classes, anonymous classes, lambda expressions, and real-world applications.
Nested Classes
Class within a class
Inner Classes
Non-static nested class
Anonymous Classes
Class without name
Lambda Expressions
Java 8+ functional
1. Introduction to Inner Classes
Inner Classes (also called Nested Classes) are classes defined within another class. They allow you to logically group classes that are only used in one place, increase encapsulation, and create more readable and maintainable code.
Types of Inner Classes
- Inner Class (Non-static): Associated with outer class instance
- Static Nested Class: Static member of outer class
- Local Class: Defined within a method or block
- Anonymous Class: Class without a name
- Lambda Expressions: Functional interface implementation (Java 8+)
Common Use Cases
- Event Listeners: GUI event handling
- Callback Mechanisms: Asynchronous programming
- Iterator Patterns: Custom collection iterators
- Builder Pattern: Object construction
- Data Hiding: Implementation details
- Adapter Classes: Interface adaptation
Inner Classes Hierarchy Visualization
(Non-static)
// Outer Class
public class InnerClassesDemo {
// Instance variable of outer class
private String outerMessage = "Hello from Outer Class";
private static String staticOuterMessage = "Static message from Outer Class";
// === 1. INNER CLASS (Non-static nested class) ===
class InnerClass {
private String innerMessage = "Hello from Inner Class";
// Inner class can access outer class members (including private)
public void display() {
System.out.println("Inner Class accessing:");
System.out.println(" - Outer instance variable: " + outerMessage);
System.out.println(" - Outer static variable: " + staticOuterMessage);
System.out.println(" - Inner instance variable: " + innerMessage);
}
// Inner class can have its own methods
public void innerMethod() {
System.out.println("Inner class method called");
}
}
// === 2. STATIC NESTED CLASS ===
static class StaticNestedClass {
private String nestedMessage = "Hello from Static Nested Class";
public void display() {
System.out.println("Static Nested Class accessing:");
System.out.println(" - Outer static variable: " + staticOuterMessage);
System.out.println(" - Nested instance variable: " + nestedMessage);
// Cannot access non-static outer members directly
// System.out.println(" - Outer instance variable: " + outerMessage); // ERROR
}
}
// === 3. LOCAL CLASS (defined inside a method) ===
public void demonstrateLocalClass() {
// Local variable (must be effectively final to be accessed by local class)
final String localVar = "Local variable in method";
String effectivelyFinalVar = "Effectively final variable";
// Local class definition inside method
class LocalClass {
private String localClassMessage = "Hello from Local Class";
public void display() {
System.out.println("Local Class accessing:");
System.out.println(" - Outer instance variable: " + outerMessage);
System.out.println(" - Outer static variable: " + staticOuterMessage);
System.out.println(" - Local variable: " + localVar);
System.out.println(" - Effectively final variable: " + effectivelyFinalVar);
System.out.println(" - Local class variable: " + localClassMessage);
}
}
// Create instance of local class and use it
LocalClass local = new LocalClass();
local.display();
}
// === 4. ANONYMOUS CLASS ===
interface Greeting {
void sayHello();
void sayGoodbye();
}
public void demonstrateAnonymousClass() {
// Anonymous class implementing Greeting interface
Greeting anonymousGreeting = new Greeting() {
private String greeting = "Hello from Anonymous Class";
@Override
public void sayHello() {
System.out.println(greeting);
System.out.println("Accessing outer variable: " + outerMessage);
}
@Override
public void sayGoodbye() {
System.out.println("Goodbye from Anonymous Class");
}
// Anonymous class can have additional methods (but can't call them from outside)
public void extraMethod() {
System.out.println("Extra method in anonymous class");
}
};
anonymousGreeting.sayHello();
anonymousGreeting.sayGoodbye();
// anonymousGreeting.extraMethod(); // ERROR: Not part of Greeting interface
}
// === 5. LAMBDA EXPRESSION (Java 8+) ===
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
public void demonstrateLambda() {
// Lambda expression implementing Calculator interface
Calculator adder = (a, b) -> a + b;
Calculator multiplier = (a, b) -> a * b;
System.out.println("Lambda Examples:");
System.out.println("5 + 3 = " + adder.calculate(5, 3));
System.out.println("5 * 3 = " + multiplier.calculate(5, 3));
// Lambda with multiple statements
Calculator complexCalculator = (a, b) -> {
int result = a + b;
result *= 2;
return result;
};
System.out.println("(5 + 3) * 2 = " + complexCalculator.calculate(5, 3));
}
// Method to demonstrate all types
public void demonstrateAll() {
System.out.println("=== 1. INNER CLASS ===");
InnerClass inner = new InnerClass();
inner.display();
inner.innerMethod();
System.out.println("\n=== 2. STATIC NESTED CLASS ===");
StaticNestedClass staticNested = new StaticNestedClass();
staticNested.display();
System.out.println("\n=== 3. LOCAL CLASS ===");
demonstrateLocalClass();
System.out.println("\n=== 4. ANONYMOUS CLASS ===");
demonstrateAnonymousClass();
System.out.println("\n=== 5. LAMBDA EXPRESSION ===");
demonstrateLambda();
}
public static void main(String[] args) {
InnerClassesDemo demo = new InnerClassesDemo();
demo.demonstrateAll();
System.out.println("\n=== Creating Inner Class Instance ===");
// To create inner class instance, need outer class instance
InnerClassesDemo.InnerClass inner = demo.new InnerClass();
inner.display();
System.out.println("\n=== Creating Static Nested Class Instance ===");
// Static nested class can be created without outer class instance
InnerClassesDemo.StaticNestedClass staticNested = new InnerClassesDemo.StaticNestedClass();
staticNested.display();
System.out.println("\n=== Key Differences ===");
System.out.println("Inner Class:");
System.out.println("- Associated with outer class instance");
System.out.println("- Can access outer instance members");
System.out.println("- Created with: outerInstance.new InnerClass()");
System.out.println("\nStatic Nested Class:");
System.out.println("- Not associated with outer instance");
System.out.println("- Can only access outer static members");
System.out.println("- Created with: new OuterClass.StaticNestedClass()");
}
}
2. Inner Classes vs Static Nested Classes
Understanding the difference between inner classes (non-static) and static nested classes is crucial for proper usage. Each has distinct characteristics and use cases.
Inner Class (Non-static)
- Instance association: Tied to outer class instance
- Access: Can access all outer instance members
- Memory: Has implicit reference to outer instance
- Creation: Requires outer instance:
outer.new Inner() - Use when: Need access to outer instance state
- Example: Iterator in collection class
Static Nested Class
- No instance association: Independent of outer instance
- Access: Can only access outer static members
- Memory: No reference to outer instance
- Creation: Independent:
new Outer.StaticNested() - Use when: Helper class logically related to outer
- Example: Builder pattern, utility classes
Complete Comparison Table
| Aspect | Inner Class (Non-static) | Static Nested Class |
|---|---|---|
| Association | Tied to outer class instance | Independent of outer instance |
| Memory Overhead | Has implicit reference to outer (extra 4-8 bytes) | No reference to outer instance |
| Access to Outer | All outer members (including private) | Only static members of outer |
| Instantiation | outerInstance.new Inner() |
new Outer.StaticNested() |
| Static Members | Cannot have static members (except final constants) | Can have static members |
| Can be Abstract | Yes | Yes |
| Can be Final | Yes | Yes |
| Serialization | Serializes with outer instance | Serializes independently |
| Performance | Slightly slower (indirect access) | Same as regular class |
| Use Case | Intimate helper, iterator, adapter | Builder, utility, independent helper |
public class InnerVsStaticComparison {
// Outer class with both instance and static members
private String outerInstanceField = "Instance Field";
private static String outerStaticField = "Static Field";
private int outerCounter = 0;
private static int staticCounter = 0;
// === INNER CLASS (Non-static) ===
class InnerClass {
// Cannot have static declarations (except constants)
// private static int x = 10; // COMPILE ERROR
private static final int CONSTANT = 100; // Allowed (constant)
private String innerField = "Inner Field";
public InnerClass() {
outerCounter++; // Can modify outer instance field
staticCounter++; // Can modify outer static field
}
public void display() {
System.out.println("=== Inner Class ===");
System.out.println("Accessing outer instance field: " + outerInstanceField);
System.out.println("Accessing outer static field: " + outerStaticField);
System.out.println("Outer counter: " + outerCounter);
System.out.println("Static counter: " + staticCounter);
System.out.println("Inner field: " + innerField);
System.out.println("Constant: " + CONSTANT);
// Can call outer instance methods
outerMethod();
// Can call outer static methods
staticOuterMethod();
}
// Inner class method accessing outer's private method
public void accessOuterPrivate() {
privateOuterMethod(); // Can access private outer method
}
}
// === STATIC NESTED CLASS ===
static class StaticNestedClass {
// Can have static members
private static int staticNestedCounter = 0;
private String nestedField = "Nested Field";
public StaticNestedClass() {
staticNestedCounter++;
staticCounter++; // Can modify outer static field
// outerCounter++; // COMPILE ERROR: Cannot access instance field
}
public void display() {
System.out.println("\n=== Static Nested Class ===");
// System.out.println("Accessing outer instance field: " + outerInstanceField); // ERROR
System.out.println("Accessing outer static field: " + outerStaticField);
System.out.println("Static counter: " + staticCounter);
System.out.println("Static nested counter: " + staticNestedCounter);
System.out.println("Nested field: " + nestedField);
// staticOuterMethod(); // Can call outer static methods
// outerMethod(); // ERROR: Cannot call instance methods
// Need outer instance to access instance members
InnerVsStaticComparison outer = new InnerVsStaticComparison();
System.out.println("Via outer instance: " + outer.outerInstanceField);
outer.outerMethod();
}
// Static method in static nested class
public static void staticNestedMethod() {
System.out.println("Static method in static nested class");
System.out.println("Outer static field: " + outerStaticField);
}
}
// Outer class methods
public void outerMethod() {
System.out.println("Outer instance method called");
}
private void privateOuterMethod() {
System.out.println("Private outer method called from inner class");
}
public static void staticOuterMethod() {
System.out.println("Outer static method called");
}
// Method to demonstrate memory implications
public void demonstrateMemory() {
System.out.println("\n=== Memory Implications ===");
// Creating multiple inner class instances
System.out.println("Creating 5 inner class instances:");
for (int i = 0; i < 5; i++) {
InnerClass inner = new InnerClass();
inner.display();
}
System.out.println("\nCreating 5 static nested class instances:");
for (int i = 0; i < 5; i++) {
StaticNestedClass nested = new StaticNestedClass();
nested.display();
}
System.out.println("\n=== Key Memory Differences ===");
System.out.println("Inner Class:");
System.out.println("- Each instance holds reference to outer instance");
System.out.println("- Cannot exist without outer instance");
System.out.println("- Can cause memory leaks if outer instance is held");
System.out.println("\nStatic Nested Class:");
System.out.println("- No reference to outer instance");
System.out.println("- Can exist independently");
System.out.println("- More memory efficient");
}
// Real-world example: Collection with Iterator
class SimpleCollection {
private String[] items;
private int size;
public SimpleCollection(int capacity) {
items = new String[capacity];
size = 0;
}
public void add(String item) {
if (size < items.length) {
items[size++] = item;
}
}
// Inner class for iterator (needs access to collection's internal array)
class Iterator {
private int currentIndex = 0;
public boolean hasNext() {
return currentIndex < size;
}
public String next() {
if (hasNext()) {
return items[currentIndex++];
}
throw new java.util.NoSuchElementException();
}
public void remove() {
// Implementation needs access to items array
// This is why iterator is often an inner class
}
}
public Iterator iterator() {
return new Iterator();
}
}
// Real-world example: Builder pattern with static nested class
static class Computer {
private String CPU;
private String RAM;
private String storage;
private String GPU;
private Computer(Builder builder) {
this.CPU = builder.CPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
this.GPU = builder.GPU;
}
// Static nested class for Builder
static class Builder {
private String CPU;
private String RAM;
private String storage;
private String GPU;
public Builder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
public Builder setRAM(String RAM) {
this.RAM = RAM;
return this;
}
public Builder setStorage(String storage) {
this.storage = storage;
return this;
}
public Builder setGPU(String GPU) {
this.GPU = GPU;
return this;
}
public Computer build() {
return new Computer(this);
}
}
@Override
public String toString() {
return "Computer [CPU=" + CPU + ", RAM=" + RAM +
", Storage=" + storage + ", GPU=" + GPU + "]";
}
}
public static void main(String[] args) {
InnerVsStaticComparison demo = new InnerVsStaticComparison();
System.out.println("=== Creating and Using Inner Class ===");
// Inner class requires outer instance
InnerVsStaticComparison.InnerClass inner = demo.new InnerClass();
inner.display();
inner.accessOuterPrivate();
System.out.println("\n=== Creating and Using Static Nested Class ===");
// Static nested class doesn't need outer instance
InnerVsStaticComparison.StaticNestedClass staticNested =
new InnerVsStaticComparison.StaticNestedClass();
staticNested.display();
StaticNestedClass.staticNestedMethod();
// Demonstrate memory implications
demo.demonstrateMemory();
System.out.println("\n=== Real-World Examples ===");
// Collection with Iterator (Inner Class)
System.out.println("\n1. Collection with Iterator (Inner Class):");
SimpleCollection collection = demo.new SimpleCollection(5);
collection.add("Apple");
collection.add("Banana");
collection.add("Cherry");
SimpleCollection.Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
System.out.println("Item: " + iterator.next());
}
// Computer Builder (Static Nested Class)
System.out.println("\n2. Computer Builder (Static Nested Class):");
Computer computer = new Computer.Builder()
.setCPU("Intel i9")
.setRAM("32GB")
.setStorage("1TB SSD")
.setGPU("RTX 4080")
.build();
System.out.println(computer);
System.out.println("\n=== When to Use Which? ===");
System.out.println("Use INNER CLASS when:");
System.out.println("- Class needs access to outer instance state");
System.out.println("- Implementing iterator/adapter pattern");
System.out.println("- Class is tightly coupled to outer instance");
System.out.println("- You need callback with access to outer state");
System.out.println("\nUse STATIC NESTED CLASS when:");
System.out.println("- Class is logically related but independent");
System.out.println("- Implementing builder pattern");
System.out.println("- Creating utility/helper classes");
System.out.println("- No need to access outer instance state");
System.out.println("- Better memory efficiency needed");
System.out.println("\n=== Best Practice ===");
System.out.println("Default to static nested class unless you need");
System.out.println("access to outer instance members. This reduces");
System.out.println("memory overhead and coupling.");
}
}
3. Local & Anonymous Classes
Local classes are defined within a method or block, while anonymous classes are classes without a name, typically used for one-time implementations of interfaces or abstract classes.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
public class LocalAnonymousClasses {
// === LOCAL CLASSES ===
// Local class defined inside a method
public void demonstrateLocalClass() {
System.out.println("=== LOCAL CLASS DEMONSTRATION ===\n");
// Local variables that will be accessed by local class
final String finalLocalVar = "Final Local Variable";
String effectivelyFinalVar = "Effectively Final Variable";
int counter = 0; // This cannot be modified if accessed by local class
// Local class definition inside method
class LocalGreeter {
private String localField = "Local Class Field";
public void greet(String name) {
System.out.println("Hello " + name + " from LocalGreeter!");
System.out.println("Accessing:");
System.out.println(" - Local field: " + localField);
System.out.println(" - Final local variable: " + finalLocalVar);
System.out.println(" - Effectively final variable: " + effectivelyFinalVar);
// System.out.println(" - Counter: " + counter); // ERROR if counter is modified
// Can access outer class members
System.out.println(" - Outer static field: " + staticOuterField);
// Need outer instance to access instance members
}
// Local class can have static constants
public static final int LOCAL_CONSTANT = 42;
// Local class cannot have static methods (except in Java 16+)
// public static void staticMethod() {} // ERROR in Java < 16
}
// Cannot modify effectivelyFinalVar after local class definition
// effectivelyFinalVar = "Changed"; // ERROR
// Create and use local class instance
LocalGreeter greeter = new LocalGreeter();
greeter.greet("Alice");
greeter.greet("Bob");
System.out.println("\nLocal constant: " + LocalGreeter.LOCAL_CONSTANT);
// Local class can implement interfaces
interface LocalInterface {
void perform();
}
class LocalImplementation implements LocalInterface {
@Override
public void perform() {
System.out.println("Local implementation performing...");
}
}
LocalImplementation localImpl = new LocalImplementation();
localImpl.perform();
}
// Local class in a block
public void demonstrateLocalClassInBlock() {
System.out.println("\n=== LOCAL CLASS IN BLOCK ===");
for (int i = 0; i < 3; i++) {
// Local class defined inside loop block
class BlockLocal {
private int iteration;
public BlockLocal(int iteration) {
this.iteration = iteration;
}
public void display() {
System.out.println("BlockLocal iteration " + iteration);
// Can access loop variable i? NO - i is not effectively final
// System.out.println("Loop i: " + i); // ERROR
}
}
BlockLocal blockLocal = new BlockLocal(i);
blockLocal.display();
}
}
// === ANONYMOUS CLASSES ===
interface EventListener {
void onEvent(String eventName);
void onError(String errorMessage);
}
abstract class AbstractProcessor {
public abstract void process(String data);
public void log(String message) {
System.out.println("Log: " + message);
}
}
public void demonstrateAnonymousClass() {
System.out.println("\n=== ANONYMOUS CLASS DEMONSTRATION ===\n");
// 1. Anonymous class implementing interface
EventListener anonymousListener = new EventListener() {
private int eventCount = 0;
@Override
public void onEvent(String eventName) {
eventCount++;
System.out.println("Event #" + eventCount + ": " + eventName);
System.out.println("Accessing outer static field: " + staticOuterField);
}
@Override
public void onError(String errorMessage) {
System.out.println("Error in anonymous listener: " + errorMessage);
}
// Additional method (not accessible from EventListener reference)
public void extraMethod() {
System.out.println("Extra method in anonymous class");
}
};
anonymousListener.onEvent("Application Started");
anonymousListener.onEvent("User Logged In");
anonymousListener.onError("Connection lost");
// anonymousListener.extraMethod(); // ERROR: Not in EventListener interface
// 2. Anonymous class extending abstract class
AbstractProcessor anonymousProcessor = new AbstractProcessor() {
private String processorName = "Anonymous Processor";
@Override
public void process(String data) {
System.out.println(processorName + " processing: " + data);
log("Processing completed for: " + data);
}
// Can override non-abstract methods too
@Override
public void log(String message) {
System.out.println("[" + processorName + "] " + message);
}
};
anonymousProcessor.process("Sample Data");
// 3. Anonymous class with constructor arguments
// (Using a concrete class as base)
class BaseClass {
protected String name;
public BaseClass(String name) {
this.name = name;
}
public void display() {
System.out.println("BaseClass: " + name);
}
}
BaseClass extendedAnonymous = new BaseClass("Anonymous Extension") {
private String extraInfo = "Extra Information";
@Override
public void display() {
super.display();
System.out.println("Additional: " + extraInfo);
}
public void newMethod() {
System.out.println("New method in anonymous extension");
}
};
extendedAnonymous.display();
// extendedAnonymous.newMethod(); // ERROR: Not in BaseClass
// 4. Anonymous class for one-time comparator
List names = new ArrayList<>();
names.add("Charlie");
names.add("Alice");
names.add("Bob");
System.out.println("\nOriginal list: " + names);
// Anonymous Comparator
names.sort(new Comparator() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
System.out.println("Sorted list: " + names);
}
// === PRACTICAL EXAMPLE: EVENT HANDLING ===
class Button {
private String label;
private EventListener listener;
public Button(String label) {
this.label = label;
}
public void setOnClickListener(EventListener listener) {
this.listener = listener;
}
public void click() {
System.out.println("\nButton '" + label + "' clicked!");
if (listener != null) {
listener.onEvent("ButtonClick");
}
}
}
public void demonstrateEventHandling() {
System.out.println("\n=== EVENT HANDLING WITH ANONYMOUS CLASSES ===\n");
Button submitButton = new Button("Submit");
Button cancelButton = new Button("Cancel");
// Anonymous class for submit button
submitButton.setOnClickListener(new EventListener() {
private int clickCount = 0;
@Override
public void onEvent(String eventName) {
clickCount++;
System.out.println("Submit button clicked " + clickCount + " times");
System.out.println("Processing form submission...");
}
@Override
public void onError(String errorMessage) {
System.out.println("Submission error: " + errorMessage);
}
});
// Anonymous class for cancel button
cancelButton.setOnClickListener(new EventListener() {
@Override
public void onEvent(String eventName) {
System.out.println("Cancelling operation...");
System.out.println("Reset form fields...");
}
@Override
public void onError(String errorMessage) {
System.out.println("Cancel error: " + errorMessage);
}
});
// Simulate button clicks
submitButton.click();
submitButton.click();
cancelButton.click();
}
// === LIMITATIONS AND GOTCHAS ===
public void demonstrateLimitations() {
System.out.println("\n=== LIMITATIONS OF LOCAL/ANONYMOUS CLASSES ===\n");
// 1. Cannot have static members (except constants in local classes)
class LocalWithStatic {
public static final int CONSTANT = 100; // OK
// public static int variable = 200; // ERROR (in Java < 16)
// public static void method() {} // ERROR (in Java < 16)
}
// 2. Cannot declare constructors in anonymous classes
// (but can have instance initializer block)
Runnable anonymousWithInit = new Runnable() {
private String message;
// Instance initializer (acts like constructor)
{
message = "Initialized in instance block";
System.out.println("Anonymous class initialized: " + message);
}
@Override
public void run() {
System.out.println("Running: " + message);
}
};
anonymousWithInit.run();
// 3. Serialization issues
System.out.println("\nSerialization Warning:");
System.out.println("- Local and anonymous classes have synthetic fields");
System.out.println("- Serialization may not work as expected");
System.out.println("- Consider static nested classes for serialization");
// 4. Memory considerations
System.out.println("\nMemory Considerations:");
System.out.println("- Each instance captures local variables");
System.out.println("- Can cause memory leaks if holding references");
System.out.println("- Anonymous classes create .class files (Class$1.class)");
}
// Outer class fields
private String instanceField = "Instance Field";
private static String staticOuterField = "Static Outer Field";
public static void main(String[] args) {
LocalAnonymousClasses demo = new LocalAnonymousClasses();
// Demonstrate local classes
demo.demonstrateLocalClass();
demo.demonstrateLocalClassInBlock();
// Demonstrate anonymous classes
demo.demonstrateAnonymousClass();
// Demonstrate event handling
demo.demonstrateEventHandling();
// Demonstrate limitations
demo.demonstrateLimitations();
System.out.println("\n=== WHEN TO USE LOCAL/ANONYMOUS CLASSES ===");
System.out.println("\nUse LOCAL CLASS when:");
System.out.println("- Need a class used only within one method");
System.out.println("- Class needs to access method-local variables");
System.out.println("- Want to avoid polluting outer class namespace");
System.out.println("- Implementing a simple helper used in one place");
System.out.println("\nUse ANONYMOUS CLASS when:");
System.out.println("- Need one-time implementation of interface/abstract class");
System.out.println("- Implementing event listeners/callbacks");
System.out.println("- Creating comparators, runnables, etc.");
System.out.println("- Quick implementation without creating named class");
System.out.println("\n=== BEST PRACTICES ===");
System.out.println("1. Keep local/anonymous classes small and simple");
System.out.println("2. Use lambda expressions instead of anonymous classes when possible");
System.out.println("3. Avoid modifying captured variables");
System.out.println("4. Consider moving complex logic to named static nested classes");
System.out.println("5. Be mindful of serialization requirements");
}
}
// Evolution from Anonymous Classes to Lambda Expressions
import java.util.*;
import java.util.function.*;
public class AnonymousClassEvolution {
// === JAVA 7 AND BEFORE: ANONYMOUS CLASSES ===
interface OldStyleOperation {
int execute(int a, int b);
}
public void java7Style() {
System.out.println("=== JAVA 7 STYLE (Anonymous Classes) ===\n");
// Anonymous class for addition
OldStyleOperation adder = new OldStyleOperation() {
@Override
public int execute(int a, int b) {
return a + b;
}
};
// Anonymous class for multiplication
OldStyleOperation multiplier = new OldStyleOperation() {
@Override
public int execute(int a, int b) {
return a * b;
}
};
// Anonymous class with state
OldStyleOperation counter = new OldStyleOperation() {
private int operationCount = 0;
@Override
public int execute(int a, int b) {
operationCount++;
System.out.println("Operation count: " + operationCount);
return a - b;
}
};
System.out.println("5 + 3 = " + adder.execute(5, 3));
System.out.println("5 * 3 = " + multiplier.execute(5, 3));
System.out.println("5 - 3 = " + counter.execute(5, 3));
System.out.println("10 - 4 = " + counter.execute(10, 4));
// Thread with anonymous Runnable
Thread oldThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread running (Java 7 style)");
}
});
oldThread.start();
// Comparator with anonymous class
List names = Arrays.asList("Charlie", "Alice", "Bob");
Collections.sort(names, new Comparator() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
System.out.println("Sorted: " + names);
}
// === JAVA 8: LAMBDA EXPRESSIONS ===
@FunctionalInterface
interface Operation {
int apply(int a, int b);
}
public void java8Style() {
System.out.println("\n=== JAVA 8 STYLE (Lambda Expressions) ===\n");
// Lambda expression for addition
Operation adder = (a, b) -> a + b;
// Lambda expression for multiplication
Operation multiplier = (a, b) -> a * b;
// Lambda with multiple statements
Operation complex = (a, b) -> {
int result = a + b;
return result * 2;
};
System.out.println("5 + 3 = " + adder.apply(5, 3));
System.out.println("5 * 3 = " + multiplier.apply(5, 3));
System.out.println("(5 + 3) * 2 = " + complex.apply(5, 3));
// Thread with lambda
Thread newThread = new Thread(() -> {
System.out.println("Thread running (Java 8 style)");
});
newThread.start();
// Comparator with lambda
List names = Arrays.asList("Charlie", "Alice", "Bob");
names.sort((s1, s2) -> s1.compareTo(s2));
System.out.println("Sorted: " + names);
// Using built-in functional interfaces
Consumer printer = s -> System.out.println("Printing: " + s);
printer.accept("Hello Lambda!");
Predicate isEven = n -> n % 2 == 0;
System.out.println("Is 4 even? " + isEven.test(4));
System.out.println("Is 5 even? " + isEven.test(5));
// Method references
names.forEach(System.out::println);
}
// === WHEN TO STICK WITH ANONYMOUS CLASSES ===
public void whenToUseAnonymous() {
System.out.println("\n=== WHEN TO USE ANONYMOUS CLASSES (Even in Java 8+) ===\n");
// 1. When you need state (instance variables)
Runnable statefulTask = new Runnable() {
private int executionCount = 0;
private String taskName = "Stateful Task";
@Override
public void run() {
executionCount++;
System.out.println(taskName + " executed " + executionCount + " times");
}
};
statefulTask.run();
statefulTask.run();
// 2. When implementing interface with multiple methods
interface MultiMethod {
void method1();
void method2();
default void defaultMethod() {
System.out.println("Default implementation");
}
}
MultiMethod multi = new MultiMethod() {
@Override
public void method1() {
System.out.println("Method 1 implemented");
}
@Override
public void method2() {
System.out.println("Method 2 implemented");
}
@Override
public void defaultMethod() {
System.out.println("Overridden default method");
}
};
multi.method1();
multi.method2();
multi.defaultMethod();
// 3. When extending a class (not just implementing interface)
abstract class AbstractGreeter {
abstract void greet();
void sayGoodbye() {
System.out.println("Goodbye!");
}
}
AbstractGreeter greeter = new AbstractGreeter() {
@Override
void greet() {
System.out.println("Hello from anonymous subclass!");
}
@Override
void sayGoodbye() {
System.out.println("Farewell!");
}
};
greeter.greet();
greeter.sayGoodbye();
// 4. When you need to call super methods
class Base {
void show() {
System.out.println("Base class show()");
}
}
Base extended = new Base() {
@Override
void show() {
super.show(); // Can call super in anonymous class
System.out.println("Anonymous class show()");
}
};
extended.show();
}
// === PRACTICAL EXAMPLE: EVENT SYSTEM EVOLUTION ===
interface Event {
void fire();
}
class EventSystem {
private List listeners = new ArrayList<>();
// Java 7 style: Add event with anonymous class
public void addJava7Event(String eventName) {
listeners.add(new Event() {
@Override
public void fire() {
System.out.println("Java 7 Event: " + eventName);
}
});
}
// Java 8 style: Add event with lambda
public void addJava8Event(String eventName) {
listeners.add(() -> System.out.println("Java 8 Event: " + eventName));
}
// Generic method with Consumer
public void addEvent(Consumer handler, String eventName) {
listeners.add(() -> handler.accept(eventName));
}
public void fireAll() {
listeners.forEach(Event::fire);
}
}
public void demonstrateEventEvolution() {
System.out.println("\n=== EVENT SYSTEM EVOLUTION ===\n");
EventSystem system = new EventSystem();
// Java 7 style
system.addJava7Event("System Startup");
system.addJava7Event("User Login");
// Java 8 style
system.addJava8Event("Data Loaded");
system.addJava8Event("Processing Complete");
// Modern style with Consumer
system.addEvent(name -> System.out.println("Custom: " + name), "Custom Event");
system.fireAll();
}
// === PERFORMANCE COMPARISON ===
public void performanceComparison() {
System.out.println("\n=== PERFORMANCE CONSIDERATIONS ===\n");
int iterations = 1000000;
// Anonymous class
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
Runnable r = new Runnable() {
@Override
public void run() {
// Do nothing
}
};
}
long anonymousTime = System.nanoTime() - start;
// Lambda expression
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
Runnable r = () -> {
// Do nothing
};
}
long lambdaTime = System.nanoTime() - start;
System.out.println("Creating " + iterations + " instances:");
System.out.println("Anonymous classes: " + anonymousTime / 1000000 + " ms");
System.out.println("Lambda expressions: " + lambdaTime / 1000000 + " ms");
System.out.println("\nNote: Lambda expressions are generally more efficient");
System.out.println("as they don't create new .class files and have");
System.out.println("better runtime optimization.");
}
public static void main(String[] args) {
AnonymousClassEvolution demo = new AnonymousClassEvolution();
// Show evolution
demo.java7Style();
demo.java8Style();
// When to still use anonymous classes
demo.whenToUseAnonymous();
// Practical example
demo.demonstrateEventEvolution();
// Performance
demo.performanceComparison();
System.out.println("\n=== EVOLUTION SUMMARY ===");
System.out.println("\nJava 7 and before:");
System.out.println("- Verbose anonymous classes");
System.out.println("- Lots of boilerplate code");
System.out.println("- Separate .class files for each anonymous class");
System.out.println("\nJava 8 and later:");
System.out.println("- Concise lambda expressions");
System.out.println("- Functional programming support");
System.out.println("- Method references for even more conciseness");
System.out.println("- Better performance in most cases");
System.out.println("\n=== MIGRATION GUIDELINES ===");
System.out.println("1. Convert single-method interfaces to lambdas");
System.out.println("2. Keep anonymous classes for:");
System.out.println(" - Multiple method implementations");
System.out.println(" - Classes with state (instance variables)");
System.out.println(" - Extending classes (not just interfaces)");
System.out.println(" - When you need to call super methods");
System.out.println("3. Use method references where applicable");
System.out.println("4. Test performance for critical code paths");
}
}
4. Lambda Expressions (Java 8+)
Lambda expressions provide a clear and concise way to represent one method interface using an expression. They are a fundamental feature of functional programming in Java.
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class LambdaExpressionsComplete {
// === FUNCTIONAL INTERFACES ===
@FunctionalInterface
interface StringProcessor {
String process(String input);
// Can have default methods
default StringProcessor andThen(StringProcessor after) {
return input -> after.process(this.process(input));
}
// Can have static methods
static StringProcessor toUpperCase() {
return String::toUpperCase;
}
}
@FunctionalInterface
interface TriFunction {
R apply(T t, U u, V v);
}
// === LAMBDA SYNTAX VARIATIONS ===
public void demonstrateSyntax() {
System.out.println("=== LAMBDA EXPRESSION SYNTAX ===\n");
// 1. No parameters
Runnable noParam = () -> System.out.println("Hello from lambda!");
noParam.run();
// 2. Single parameter, inferred type
Consumer singleParam = message -> System.out.println("Message: " + message);
singleParam.accept("Hello World");
// 3. Single parameter with parentheses (optional)
Consumer singleParamWithParens = (message) -> System.out.println("Message: " + message);
singleParamWithParens.accept("Hello Again");
// 4. Multiple parameters
BinaryOperator add = (a, b) -> a + b;
System.out.println("5 + 3 = " + add.apply(5, 3));
// 5. Multiple parameters with explicit types
BinaryOperator multiply = (Integer a, Integer b) -> a * b;
System.out.println("5 * 3 = " + multiply.apply(5, 3));
// 6. Multiple statements in body
BinaryOperator complex = (a, b) -> {
int result = a + b;
System.out.println("Calculating " + a + " + " + b);
return result * 2;
};
System.out.println("(5 + 3) * 2 = " + complex.apply(5, 3));
// 7. Returning a value (explicit return)
Function converter = (num) -> {
return "Number: " + num;
};
System.out.println(converter.apply(42));
// 8. Returning a value (implicit return - single expression)
Function square = num -> num * num;
System.out.println("Square of 5: " + square.apply(5));
}
// === BUILT-IN FUNCTIONAL INTERFACES ===
public void demonstrateBuiltInInterfaces() {
System.out.println("\n=== BUILT-IN FUNCTIONAL INTERFACES ===\n");
// 1. Predicate - tests a condition
Predicate isLong = s -> s.length() > 10;
System.out.println("Is 'Hello' long? " + isLong.test("Hello"));
System.out.println("Is 'Hello World!' long? " + isLong.test("Hello World!"));
// Predicate composition
Predicate isEven = n -> n % 2 == 0;
Predicate isPositive = n -> n > 0;
Predicate isEvenAndPositive = isEven.and(isPositive);
System.out.println("Is 4 even and positive? " + isEvenAndPositive.test(4));
System.out.println("Is -2 even and positive? " + isEvenAndPositive.test(-2));
// 2. Function - transforms input to output
Function lengthFunction = String::length;
System.out.println("Length of 'Hello': " + lengthFunction.apply("Hello"));
// Function composition
Function multiplyBy2 = n -> n * 2;
Function toString = Object::toString;
Function multiplyAndString = multiplyBy2.andThen(toString);
System.out.println("5 * 2 as string: " + multiplyAndString.apply(5));
// 3. Consumer - consumes a value (returns void)
Consumer printer = System.out::println;
printer.accept("Hello Consumer!");
// Consumer chain
Consumer logger = s -> System.out.println("LOG: " + s);
Consumer combined = printer.andThen(logger);
combined.accept("Important Message");
// 4. Supplier - supplies a value
Supplier randomSupplier = Math::random;
System.out.println("Random number: " + randomSupplier.get());
Supplier> listSupplier = ArrayList::new;
List newList = listSupplier.get();
newList.add("First Item");
System.out.println("New list: " + newList);
// 5. UnaryOperator - function where input and output are same type
UnaryOperator toUpper = String::toUpperCase;
System.out.println("hello in uppercase: " + toUpper.apply("hello"));
// 6. BinaryOperator - operation on two operands of same type
BinaryOperator maxOperator = Integer::max;
System.out.println("Max of 5 and 10: " + maxOperator.apply(5, 10));
// 7. BiFunction - two arguments, returns result
BiFunction concat = (s1, s2) -> s1 + " " + s2;
System.out.println("Concatenated: " + concat.apply("Hello", "World"));
}
// === VARIABLE CAPTURE IN LAMBDA ===
public void demonstrateVariableCapture() {
System.out.println("\n=== VARIABLE CAPTURE IN LAMBDA ===\n");
// Instance variable - can be modified
this.instanceCounter = 0;
Runnable instanceLambda = () -> {
instanceCounter++;
System.out.println("Instance counter: " + instanceCounter);
};
instanceLambda.run();
instanceLambda.run();
// Static variable - can be modified
staticCounter = 0;
Runnable staticLambda = () -> {
staticCounter++;
System.out.println("Static counter: " + staticCounter);
};
staticLambda.run();
staticLambda.run();
// Local variable - must be effectively final
final String finalLocal = "Final Local";
String effectivelyFinal = "Effectively Final";
// effectivelyFinal = "Changed"; // Would break the lambda
Consumer localLambda = prefix -> {
System.out.println(prefix + ": " + finalLocal);
System.out.println(prefix + ": " + effectivelyFinal);
// Can access but not modify captured locals
};
localLambda.accept("Local Capture");
// Array elements can be modified (array reference is effectively final)
int[] numbers = {1, 2, 3};
Runnable arrayLambda = () -> {
numbers[0] = 100; // Can modify array elements
System.out.println("Modified array: " + Arrays.toString(numbers));
};
arrayLambda.run();
}
// === METHOD REFERENCES ===
public void demonstrateMethodReferences() {
System.out.println("\n=== METHOD REFERENCES ===\n");
List names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 1. Static method reference
names.forEach(System.out::println);
// 2. Instance method reference on particular instance
String prefix = "Hello ";
names.forEach(prefix::concat);
// 3. Instance method reference on arbitrary instance
names.sort(String::compareToIgnoreCase);
System.out.println("Sorted: " + names);
// 4. Constructor reference
Supplier> listSupplier = ArrayList::new;
List newList = listSupplier.get();
newList.add("New Item");
System.out.println("New list: " + newList);
// 5. Array constructor reference
IntFunction arrayConstructor = int[]::new;
int[] intArray = arrayConstructor.apply(5);
System.out.println("Array length: " + intArray.length);
// Practical examples
System.out.println("\nPractical Examples:");
// Convert list of strings to uppercase
List upperNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Uppercase: " + upperNames);
// Create map from string to its length
Map lengthMap = names.stream()
.collect(Collectors.toMap(
Function.identity(), // key: string itself
String::length // value: length of string
));
System.out.println("Length map: " + lengthMap);
}
// === STREAMS AND LAMBDA ===
public void demonstrateStreams() {
System.out.println("\n=== STREAMS WITH LAMBDA ===\n");
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 1. Filter even numbers
List evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even numbers: " + evens);
// 2. Map to squares
List squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Squares: " + squares);
// 3. Reduce to sum
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println("Sum: " + sum);
// 4. Find max
Optional max = numbers.stream()
.max(Integer::compare);
max.ifPresent(m -> System.out.println("Max: " + m));
// 5. Complex stream operation
List result = numbers.stream()
.filter(n -> n > 5) // Keep numbers > 5
.map(n -> n * 2) // Double them
.sorted((a, b) -> b.compareTo(a)) // Sort descending
.map(n -> "Value: " + n) // Convert to string
.collect(Collectors.toList()); // Collect to list
System.out.println("Complex result: " + result);
// 6. Parallel stream
long parallelCount = numbers.parallelStream()
.filter(n -> n % 3 == 0)
.count();
System.out.println("Numbers divisible by 3: " + parallelCount);
}
// === PRACTICAL EXAMPLE: EVENT PROCESSING SYSTEM ===
class Event {
String type;
String data;
long timestamp;
Event(String type, String data) {
this.type = type;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
@Override
public String toString() {
return type + ": " + data + " @ " + timestamp;
}
}
@FunctionalInterface
interface EventHandler {
void handle(Event event);
default EventHandler andThen(EventHandler after) {
return event -> {
this.handle(event);
after.handle(event);
};
}
}
class EventProcessor {
private Map> handlers = new HashMap<>();
public void registerHandler(String eventType, EventHandler handler) {
handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler);
}
public void processEvent(Event event) {
List eventHandlers = handlers.get(event.type);
if (eventHandlers != null) {
eventHandlers.forEach(handler -> handler.handle(event));
}
}
public void processEvents(List events) {
events.forEach(this::processEvent);
}
}
public void demonstrateEventProcessing() {
System.out.println("\n=== EVENT PROCESSING WITH LAMBDA ===\n");
EventProcessor processor = new EventProcessor();
// Register handlers using lambda expressions
processor.registerHandler("LOGIN", event ->
System.out.println("Login event: " + event.data));
processor.registerHandler("LOGOUT", event ->
System.out.println("Logout event: " + event.data));
processor.registerHandler("ERROR", event ->
System.err.println("ERROR: " + event.data));
// More complex handler
processor.registerHandler("PURCHASE", event -> {
System.out.println("Processing purchase...");
System.out.println("Data: " + event.data);
System.out.println("Timestamp: " + event.timestamp);
});
// Chained handlers
EventHandler logger = event ->
System.out.println("LOG: " + event.type + " - " + event.data);
EventHandler notifier = event ->
System.out.println("NOTIFY: Event processed - " + event.type);
processor.registerHandler("SYSTEM", logger.andThen(notifier));
// Create and process events
List events = Arrays.asList(
new Event("LOGIN", "User Alice logged in"),
new Event("PURCHASE", "Item: Laptop, Amount: $999.99"),
new Event("ERROR", "Connection timeout"),
new Event("LOGOUT", "User Alice logged out"),
new Event("SYSTEM", "System maintenance scheduled")
);
processor.processEvents(events);
// Process with stream
System.out.println("\n=== PROCESSING WITH STREAM ===");
events.stream()
.filter(e -> !e.type.equals("ERROR"))
.sorted(Comparator.comparingLong(e -> e.timestamp))
.forEach(e -> System.out.println("Processed: " + e));
}
// === LAMBDA BEST PRACTICES ===
public void demonstrateBestPractices() {
System.out.println("\n=== LAMBDA BEST PRACTICES ===\n");
// 1. Keep lambdas short and focused
List names = Arrays.asList("Alice", "Bob", "Charlie");
// Good: Simple lambda
names.forEach(name -> System.out.println("Hello, " + name));
// Bad: Complex logic in lambda (should be extracted)
names.forEach(name -> {
String formatted = name.toUpperCase();
int length = formatted.length();
System.out.println(formatted + " has " + length + " characters");
// Too complex for lambda - consider extracting method
});
// 2. Use method references when possible
List numbers = Arrays.asList(1, 2, 3);
// Prefer this:
numbers.forEach(System.out::println);
// Over this:
numbers.forEach(n -> System.out.println(n));
// 3. Avoid modifying captured variables
final int[] counter = {0}; // Use array hack if needed
names.forEach(name -> {
counter[0]++; // Avoid if possible
System.out.println(counter[0] + ": " + name);
});
// Better: Use stream with count
long count = names.stream().count();
System.out.println("Total names: " + count);
// 4. Use meaningful parameter names
BinaryOperator adder = (first, second) -> first + second;
// Better than (a, b) -> a + b
// 5. Consider extracting complex lambdas to methods
Predicate isLongString = this::checkStringLength;
System.out.println("Is 'Hello' long? " + isLongString.test("Hello"));
}
private boolean checkStringLength(String s) {
return s != null && s.length() > 10;
}
// Instance and static variables for capture demo
private int instanceCounter;
private static int staticCounter;
public static void main(String[] args) {
LambdaExpressionsComplete demo = new LambdaExpressionsComplete();
// Demonstrate syntax variations
demo.demonstrateSyntax();
// Demonstrate built-in functional interfaces
demo.demonstrateBuiltInInterfaces();
// Demonstrate variable capture
demo.demonstrateVariableCapture();
// Demonstrate method references
demo.demonstrateMethodReferences();
// Demonstrate streams
demo.demonstrateStreams();
// Practical example
demo.demonstrateEventProcessing();
// Best practices
demo.demonstrateBestPractices();
System.out.println("\n=== KEY TAKEAWAYS ===\n");
System.out.println("1. Lambdas enable functional programming in Java");
System.out.println("2. They're more concise than anonymous classes");
System.out.println("3. Can only be used with functional interfaces");
System.out.println("4. Can capture effectively final variables");
System.out.println("5. Work seamlessly with streams and method references");
System.out.println("6. Improve code readability when used appropriately");
System.out.println("\n=== WHEN TO USE LAMBDA ===\n");
System.out.println("✅ Simple functional interface implementations");
System.out.println("✅ Event handlers and callbacks");
System.out.println("✅ Comparators and predicates");
System.out.println("✅ Stream operations");
System.out.println("✅ Anywhere you'd use a single-method anonymous class");
System.out.println("\n=== WHEN NOT TO USE LAMBDA ===\n");
System.out.println("❌ Complex logic (extract to method)");
System.out.println("❌ Multiple method implementations");
System.out.println("❌ When you need instance variables or this reference");
System.out.println("❌ When readability suffers");
}
}
5. Real-World Examples & Patterns
Complete E-commerce System with Inner Classes
// Complete e-commerce system demonstrating all types of inner classes
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class ECommerceInnerClasses {
// === OUTER CLASS: E-COMMERCE STORE ===
private String storeName;
private List products;
private List orders;
private Map customers;
public ECommerceInnerClasses(String storeName) {
this.storeName = storeName;
this.products = new ArrayList<>();
this.orders = new ArrayList<>();
this.customers = new HashMap<>();
initializeSampleData();
}
// === INNER CLASS: PRODUCT ===
class Product {
private String id;
private String name;
private String description;
private double price;
private int stock;
private Category category;
// Builder as static nested class
static class Builder {
private String id;
private String name;
private String description;
private double price;
private int stock;
private Category category;
public Builder(String id, String name) {
this.id = id;
this.name = name;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder price(double price) {
this.price = price;
return this;
}
public Builder stock(int stock) {
this.stock = stock;
return this;
}
public Builder category(Category category) {
this.category = category;
return this;
}
public Product build() {
return new Product(this);
}
}
private Product(Builder builder) {
this.id = builder.id;
this.name = builder.name;
this.description = builder.description;
this.price = builder.price;
this.stock = builder.stock;
this.category = builder.category;
}
// Local class for discount calculation
public double calculateDiscountedPrice(Customer customer) {
class DiscountCalculator {
private double basePrice;
private Customer customer;
DiscountCalculator(double basePrice, Customer customer) {
this.basePrice = basePrice;
this.customer = customer;
}
double calculate() {
double discount = 0;
// Customer type discount
if (customer.getType() == CustomerType.PREMIUM) {
discount += 0.10; // 10% for premium
} else if (customer.getType() == CustomerType.VIP) {
discount += 0.20; // 20% for VIP
}
// Category discount
if (category == Category.ELECTRONICS) {
discount += 0.05; // 5% for electronics
}
return basePrice * (1 - discount);
}
}
DiscountCalculator calculator = new DiscountCalculator(price, customer);
return calculator.calculate();
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getStock() { return stock; }
public Category getCategory() { return category; }
@Override
public String toString() {
return name + " ($" + price + ")";
}
}
// === STATIC NESTED ENUMS ===
static enum Category {
ELECTRONICS, CLOTHING, BOOKS, HOME, SPORTS
}
static enum CustomerType {
REGULAR, PREMIUM, VIP
}
static enum OrderStatus {
PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED
}
// === INNER CLASS: CUSTOMER ===
class Customer {
private String id;
private String name;
private String email;
private CustomerType type;
private List orderHistory;
public Customer(String id, String name, String email, CustomerType type) {
this.id = id;
this.name = name;
this.email = email;
this.type = type;
this.orderHistory = new ArrayList<>();
}
// Anonymous class for email notification
interface Notification {
void send(String subject, String message);
}
public void sendWelcomeEmail() {
Notification emailNotifier = new Notification() {
@Override
public void send(String subject, String message) {
System.out.println("=== Sending Email ===");
System.out.println("To: " + email);
System.out.println("Subject: " + subject);
System.out.println("Message: " + message);
System.out.println("====================\n");
}
};
emailNotifier.send(
"Welcome to " + storeName,
"Dear " + name + ",\n\nWelcome to our store! As a " + type +
" customer, you'll enjoy special benefits."
);
}
// Lambda for different notification types
public void notify(Consumer notifier, String message) {
notifier.accept(message);
}
public void addOrder(Order order) {
orderHistory.add(order);
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public CustomerType getType() { return type; }
public List getOrderHistory() { return orderHistory; }
}
// === INNER CLASS: ORDER ===
class Order {
private String orderId;
private Customer customer;
private List items;
private OrderStatus status;
private Date orderDate;
private double totalAmount;
// Builder pattern
static class Builder {
private String orderId;
private Customer customer;
private List items = new ArrayList<>();
public Builder(String orderId, Customer customer) {
this.orderId = orderId;
this.customer = customer;
}
public Builder addItem(Product product, int quantity) {
items.add(new OrderItem(product, quantity));
return this;
}
public Order build() {
return new Order(this);
}
}
// Local class for order item
class OrderItem {
private Product product;
private int quantity;
private double itemTotal;
OrderItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
this.itemTotal = product.getPrice() * quantity;
}
public double getItemTotal() {
return itemTotal;
}
@Override
public String toString() {
return product.getName() + " x" + quantity + " = $" + itemTotal;
}
}
private Order(Builder builder) {
this.orderId = builder.orderId;
this.customer = builder.customer;
this.items = builder.items;
this.status = OrderStatus.PENDING;
this.orderDate = new Date();
this.totalAmount = calculateTotal();
}
private double calculateTotal() {
return items.stream()
.mapToDouble(OrderItem::getItemTotal)
.sum();
}
// Iterator as inner class
class OrderIterator implements Iterator {
private int currentIndex = 0;
@Override
public boolean hasNext() {
return currentIndex < items.size();
}
@Override
public OrderItem next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return items.get(currentIndex++);
}
}
public Iterator iterator() {
return new OrderIterator();
}
// Anonymous class for status change listener
interface StatusChangeListener {
void onStatusChange(OrderStatus oldStatus, OrderStatus newStatus);
}
private List listeners = new ArrayList<>();
public void addStatusChangeListener(StatusChangeListener listener) {
listeners.add(listener);
}
public void setStatus(OrderStatus newStatus) {
OrderStatus oldStatus = this.status;
this.status = newStatus;
// Notify all listeners
for (StatusChangeListener listener : listeners) {
listener.onStatusChange(oldStatus, newStatus);
}
}
// Getters
public String getOrderId() { return orderId; }
public Customer getCustomer() { return customer; }
public OrderStatus getStatus() { return status; }
public double getTotalAmount() { return totalAmount; }
public List getItems() { return items; }
@Override
public String toString() {
return "Order #" + orderId + " - " + status + " - $" + totalAmount;
}
}
// === SEARCH FUNCTIONALITY WITH LAMBDA ===
interface ProductFilter {
boolean test(Product product);
}
public List searchProducts(ProductFilter filter) {
return products.stream()
.filter(filter::test)
.collect(Collectors.toList());
}
// Factory method for common filters using lambda
public static ProductFilter createPriceFilter(double maxPrice) {
return product -> product.getPrice() <= maxPrice;
}
public static ProductFilter createCategoryFilter(Category category) {
return product -> product.getCategory() == category;
}
public static ProductFilter createStockFilter(int minStock) {
return product -> product.getStock() >= minStock;
}
// === SHOPPING CART WITH INNER CLASSES ===
class ShoppingCart {
private Customer customer;
private Map items;
public ShoppingCart(Customer customer) {
this.customer = customer;
this.items = new HashMap<>();
}
// Local class for cart item
class CartItem {
Product product;
int quantity;
CartItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
double getSubtotal() {
return product.calculateDiscountedPrice(customer) * quantity;
}
}
public void addItem(Product product, int quantity) {
items.merge(product, quantity, Integer::sum);
}
public void removeItem(Product product) {
items.remove(product);
}
public double calculateTotal() {
return items.entrySet().stream()
.mapToDouble(entry -> {
Product product = entry.getKey();
int quantity = entry.getValue();
return product.calculateDiscountedPrice(customer) * quantity;
})
.sum();
}
// Anonymous class for checkout
public Order checkout() {
// Validate stock
for (Map.Entry entry : items.entrySet()) {
Product product = entry.getKey();
int requested = entry.getValue();
if (product.getStock() < requested) {
throw new IllegalStateException(
"Insufficient stock for " + product.getName()
);
}
}
// Create order
Order.Builder orderBuilder = new Order.Builder(
"ORD" + System.currentTimeMillis(),
customer
);
items.forEach(orderBuilder::addItem);
Order order = orderBuilder.build();
// Update stock
items.forEach((product, quantity) -> {
// This would normally update database
System.out.println("Reducing stock for " + product.getName() +
" by " + quantity);
});
// Add status change listener using lambda
order.addStatusChangeListener((oldStatus, newStatus) -> {
System.out.println("Order " + order.getOrderId() +
" changed from " + oldStatus +
" to " + newStatus);
});
orders.add(order);
customer.addOrder(order);
items.clear();
return order;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Shopping Cart for ").append(customer.getName()).append(":\n");
items.forEach((product, quantity) -> {
double subtotal = product.calculateDiscountedPrice(customer) * quantity;
sb.append(" ").append(product.getName())
.append(" x").append(quantity)
.append(" = $").append(subtotal)
.append("\n");
});
sb.append("Total: $").append(calculateTotal());
return sb.toString();
}
}
// === STORE OPERATIONS ===
public void addProduct(Product product) {
products.add(product);
}
public void registerCustomer(Customer customer) {
customers.put(customer.getId(), customer);
customer.sendWelcomeEmail();
}
public Customer getCustomer(String id) {
return customers.get(id);
}
public ShoppingCart createShoppingCart(Customer customer) {
return new ShoppingCart(customer);
}
public List getCustomerOrders(String customerId) {
Customer customer = customers.get(customerId);
return customer != null ? customer.getOrderHistory() : Collections.emptyList();
}
// Report generation with local class
public void generateSalesReport() {
class SalesReport {
double totalRevenue;
int totalOrders;
Map revenueByCategory;
Map revenueByCustomerType;
SalesReport() {
this.totalRevenue = orders.stream()
.mapToDouble(Order::getTotalAmount)
.sum();
this.totalOrders = orders.size();
this.revenueByCategory = new HashMap<>();
this.revenueByCustomerType = new HashMap<>();
// Calculate revenue by category
for (Order order : orders) {
for (Order.OrderItem item : order.getItems()) {
Category cat = item.product.getCategory();
revenueByCategory.merge(cat, item.getItemTotal(), Double::sum);
}
// Calculate revenue by customer type
CustomerType type = order.getCustomer().getType();
revenueByCustomerType.merge(type, order.getTotalAmount(), Double::sum);
}
}
void print() {
System.out.println("\n=== SALES REPORT ===");
System.out.println("Total Revenue: $" + totalRevenue);
System.out.println("Total Orders: " + totalOrders);
System.out.println("\nRevenue by Category:");
revenueByCategory.forEach((cat, rev) ->
System.out.println(" " + cat + ": $" + rev));
System.out.println("\nRevenue by Customer Type:");
revenueByCustomerType.forEach((type, rev) ->
System.out.println(" " + type + ": $" + rev));
}
}
SalesReport report = new SalesReport();
report.print();
}
// Initialize sample data
private void initializeSampleData() {
// Create products
products.add(new Product.Builder("P001", "Laptop")
.description("High-performance laptop")
.price(999.99)
.stock(50)
.category(Category.ELECTRONICS)
.build());
products.add(new Product.Builder("P002", "T-Shirt")
.description("Cotton t-shirt")
.price(19.99)
.stock(200)
.category(Category.CLOTHING)
.build());
products.add(new Product.Builder("P003", "Java Programming Book")
.description("Learn Java programming")
.price(49.99)
.stock(100)
.category(Category.BOOKS)
.build());
// Register customers
registerCustomer(new Customer("C001", "Alice Johnson",
"alice@email.com", CustomerType.PREMIUM));
registerCustomer(new Customer("C002", "Bob Smith",
"bob@email.com", CustomerType.REGULAR));
registerCustomer(new Customer("C003", "Charlie Brown",
"charlie@email.com", CustomerType.VIP));
}
public static void main(String[] args) {
System.out.println("=== E-COMMERCE SYSTEM WITH INNER CLASSES ===\n");
ECommerceInnerClasses store = new ECommerceInnerClasses("TechMart");
// Get customers
Customer alice = store.getCustomer("C001");
Customer bob = store.getCustomer("C002");
// Create shopping carts
ShoppingCart aliceCart = store.createShoppingCart(alice);
ShoppingCart bobCart = store.createShoppingCart(bob);
// Get products
Product laptop = store.products.get(0);
Product tshirt = store.products.get(1);
Product book = store.products.get(2);
// Add items to carts
System.out.println("=== ADDING ITEMS TO CARTS ===\n");
aliceCart.addItem(laptop, 1);
aliceCart.addItem(book, 2);
System.out.println(aliceCart);
bobCart.addItem(tshirt, 3);
bobCart.addItem(book, 1);
System.out.println("\n" + bobCart);
// Checkout
System.out.println("\n=== CHECKOUT ===\n");
try {
Order aliceOrder = aliceCart.checkout();
System.out.println("Alice's order created: " + aliceOrder);
aliceOrder.setStatus(OrderStatus.PROCESSING);
aliceOrder.setStatus(OrderStatus.SHIPPED);
Order bobOrder = bobCart.checkout();
System.out.println("Bob's order created: " + bobOrder);
} catch (Exception e) {
System.out.println("Checkout error: " + e.getMessage());
}
// Search products using lambda
System.out.println("\n=== SEARCH PRODUCTS ===\n");
System.out.println("Products under $50:");
List cheapProducts = store.searchProducts(
createPriceFilter(50.0)
);
cheapProducts.forEach(p -> System.out.println(" " + p));
System.out.println("\nElectronics products:");
List electronics = store.searchProducts(
createCategoryFilter(Category.ELECTRONICS)
);
electronics.forEach(p -> System.out.println(" " + p));
// Custom search with lambda
System.out.println("\nBooks in stock:");
List booksInStock = store.searchProducts(
p -> p.getCategory() == Category.BOOKS && p.getStock() > 0
);
booksInStock.forEach(p -> System.out.println(" " + p));
// Generate report
store.generateSalesReport();
System.out.println("\n=== INNER CLASSES USED IN THIS SYSTEM ===\n");
System.out.println("1. Inner Class: Product (accesses store state)");
System.out.println("2. Static Nested Class: Builder pattern");
System.out.println("3. Local Class: DiscountCalculator, SalesReport");
System.out.println("4. Anonymous Class: Email notification, Event listeners");
System.out.println("5. Lambda Expressions: Search filters, Stream operations");
System.out.println("6. Iterator Pattern: Order iterator as inner class");
System.out.println("\n=== BENEFITS DEMONSTRATED ===\n");
System.out.println("✅ Encapsulation: Inner classes access outer private members");
System.out.println("✅ Organization: Related classes grouped together");
System.out.println("✅ Readability: Code is more cohesive");
System.out.println("✅ Flexibility: Easy to add new functionality");
System.out.println("✅ Maintainability: Changes localized to relevant areas");
}
}
6. Best Practices & Common Pitfalls
- Memory Leaks: Inner classes holding references to outer instances
- Serialization Issues: Inner classes have synthetic fields
- Overusing Inner Classes: Making code harder to understand
- Static Context Errors: Trying to access instance members from static nested class
- Anonymous Class Bloat: Creating large anonymous classes
- Capturing Mutable Variables: Modifying captured variables
Inner Classes Best Practices
- Use static nested classes by default
- Only use inner classes when need access to outer instance
- Keep inner classes small and focused
- Avoid deep nesting (more than 2 levels)
- Use lambda expressions instead of anonymous classes when possible
- Be mindful of serialization requirements
- Document the relationship between inner and outer classes
Performance Guidelines
- Inner classes have memory overhead (outer reference)
- Static nested classes are more memory efficient
- Avoid creating many inner class instances in loops
- Lambdas are generally faster than anonymous classes
- Consider using static nested classes for frequently instantiated helpers
- Profile before optimizing inner class usage
When to Use Each Type of Inner Class
Static Nested Class: Default choice - use when class is logically related but doesn't need outer instance
Inner Class: When helper needs access to outer instance state (iterators, adapters)
Local Class: When class is only used within one method and needs local variables
Anonymous Class: One-time implementations, event listeners (use lambda when possible)
Lambda: Single-method interfaces, functional programming, stream operations
import java.io.*;
import java.util.*;
public class InnerClassesChecklist {
public static void innerClassesChecklist() {
System.out.println("=== INNER CLASSES IMPLEMENTATION CHECKLIST ===\n");
System.out.println("✅ 1. Choose the Right Type");
System.out.println(" - Does it need access to outer instance? → Inner Class");
System.out.println(" - Is it logically related but independent? → Static Nested");
System.out.println(" - Used only in one method? → Local Class");
System.out.println(" - One-time implementation? → Anonymous/Lambda");
System.out.println("\n✅ 2. Memory Considerations");
System.out.println(" - Inner classes hold reference to outer (potential leaks)");
System.out.println(" - Static nested classes are more memory efficient");
System.out.println(" - Consider weak references if needed");
System.out.println("\n✅ 3. Serialization Readiness");
System.out.println(" - Inner classes have synthetic fields");
System.out.println(" - May need custom readObject/writeObject");
System.out.println(" - Consider static nested classes for serialization");
System.out.println("\n✅ 4. Access Control");
System.out.println(" - Use appropriate access modifiers");
System.out.println(" - Private inner classes for implementation details");
System.out.println(" - Protected for extension in same package");
System.out.println("\n✅ 5. Testing Strategy");
System.out.println(" - Test inner classes independently when possible");
System.out.println(" - Mock outer instance for inner class testing");
System.out.println(" - Test serialization if required");
System.out.println("\n✅ 6. Documentation");
System.out.println(" - Document relationship to outer class");
System.out.println(" - Explain when to use static vs non-static");
System.out.println(" - Note any serialization considerations");
System.out.println("\n✅ 7. Code Review Points");
System.out.println(" - Is nesting necessary?");
System.out.println(" - Could it be a top-level class?");
System.out.println(" - Is the coupling appropriate?");
}
// Example: Well-designed inner class usage
// Outer class designed for extension with inner classes
static abstract class DataProcessor {
protected String dataSource;
public DataProcessor(String dataSource) {
this.dataSource = dataSource;
}
// Template method using inner class
public final void process() {
Validator validator = createValidator();
if (!validator.validate()) {
throw new IllegalArgumentException("Invalid data");
}
Processor processor = createProcessor();
processor.processData();
Notifier notifier = createNotifier();
notifier.notifyCompletion();
}
// Factory methods to be implemented by subclasses
protected abstract Validator createValidator();
protected abstract Processor createProcessor();
protected abstract Notifier createNotifier();
// Inner interface for validator
protected interface Validator {
boolean validate();
}
// Inner interface for processor
protected interface Processor {
void processData();
}
// Inner interface for notifier
protected interface Notifier {
void notifyCompletion();
}
}
// Concrete implementation
static class FileProcessor extends DataProcessor {
public FileProcessor(String filePath) {
super(filePath);
}
@Override
protected Validator createValidator() {
// Local class for file validation
class FileValidator implements Validator {
@Override
public boolean validate() {
File file = new File(dataSource);
return file.exists() && file.canRead();
}
}
return new FileValidator();
}
@Override
protected Processor createProcessor() {
// Lambda for simple processing
return () -> {
System.out.println("Processing file: " + dataSource);
// Actual file processing logic
};
}
@Override
protected Notifier createNotifier() {
// Anonymous class for notification
return new Notifier() {
@Override
public void notifyCompletion() {
System.out.println("File processing completed: " + dataSource);
// Additional notification logic
}
};
}
}
// Example: Avoiding common pitfalls
static class PitfallExamples {
// PITFALL 1: Memory leak with inner class
static class MemoryLeakExample {
private byte[] largeData = new byte[1000000]; // 1MB
private List callbacks = new ArrayList<>();
// Inner class holding reference to outer
class Callback implements Runnable {
@Override
public void run() {
System.out.println("Callback using: " + largeData.length);
}
}
public void addCallback() {
callbacks.add(new Callback()); // Keeps reference to outer instance
}
// Solution: Use static nested class with weak reference
static class SafeCallback implements Runnable {
private WeakReference outerRef;
SafeCallback(MemoryLeakExample outer) {
this.outerRef = new WeakReference<>(outer);
}
@Override
public void run() {
MemoryLeakExample outer = outerRef.get();
if (outer != null) {
System.out.println("Safe callback");
}
}
}
}
// PITFALL 2: Serialization issues
static class SerializationExample implements Serializable {
private String data = "Important Data";
// Non-static inner class - serialization problem!
class InnerData implements Serializable {
private String inner = "Inner Data";
}
// Solution: Make it static
static class SerializableData implements Serializable {
private String data = "Serializable Data";
}
}
// PITFALL 3: Overly complex anonymous class
static class ComplexAnonymous {
public void problematicMethod() {
// Too complex for anonymous class
Runnable complexTask = new Runnable() {
private int state = 0;
private List buffer = new ArrayList<>();
@Override
public void run() {
// 50 lines of complex logic...
System.out.println("Too complex for anonymous class");
}
private void helperMethod() {
// Even more complexity...
}
};
// Solution: Extract to named class
complexTask.run();
}
// Better: Extract to static nested class
static class ComplexTask implements Runnable {
private int state = 0;
private List buffer = new ArrayList<>();
@Override
public void run() {
// Complex logic here
}
private void helperMethod() {
// Helper methods
}
}
}
}
public static void main(String[] args) {
innerClassesChecklist();
System.out.println("\n=== EXAMPLE: WELL-DESIGNED DATA PROCESSOR ===\n");
DataProcessor processor = new FileProcessor("data.txt");
processor.process();
System.out.println("\n=== COMMON PITFALLS AND SOLUTIONS ===\n");
System.out.println("PITFALL 1: Memory Leaks");
System.out.println("Problem: Inner classes hold strong references to outer");
System.out.println("Solution: Use static nested classes with WeakReference");
System.out.println("\nPITFALL 2: Serialization Issues");
System.out.println("Problem: Inner classes have synthetic fields");
System.out.println("Solution: Use static nested classes for serialization");
System.out.println("\nPITFALL 3: Anonymous Class Complexity");
System.out.println("Problem: Large anonymous classes are hard to read/maintain");
System.out.println("Solution: Extract to named static nested class");
System.out.println("\nPITFALL 4: Static Context Errors");
System.out.println("Problem: Trying to access instance members from static nested class");
System.out.println("Solution: Pass outer instance as parameter if needed");
System.out.println("\nPITFALL 5: Capturing Mutable Variables");
System.out.println("Problem: Modifying captured variables in lambda/local class");
System.out.println("Solution: Use atomic references or avoid modification");
System.out.println("\n=== FINAL RECOMMENDATIONS ===\n");
System.out.println("1. Prefer static nested classes over inner classes");
System.out.println("2. Use lambdas instead of anonymous classes when possible");
System.out.println("3. Keep inner classes small and focused");
System.out.println("4. Be mindful of memory and serialization implications");
System.out.println("5. Document the design decisions for inner classes");
System.out.println("6. Test inner classes thoroughly, especially for memory leaks");
System.out.println("\n=== WHEN TO REEVALUATE ===\n");
System.out.println("Consider moving to top-level class when:");
System.out.println("- Inner class grows beyond 100 lines");
System.out.println("- Inner class is used by multiple outer classes");
System.out.println("- Inner class has complex dependencies");
System.out.println("- Testing becomes difficult");
}
}