OOP Advanced
Inner Classes
Classes Within Classes
Your LinkedList needs a Node class, but Node has no meaning outside LinkedList. Inner classes let you define classes inside other classes - encapsulating helpers and keeping related code together.
Member inner class
Define a class inside another class with access to outer members.
// Member Inner Class
class OuterClass {
private String name;
private int count = 0;
OuterClass(String name) {
this.name = name;
}
// Member inner class
class InnerClass {
private String innerName;
InnerClass(String innerName) {
this.innerName = innerName;
}
void display() {
// Can access outer's private members!
System.out.println("Outer name: " + name);
System.out.println("Inner name: " + innerName);
System.out.println("Count: " + count);
}
void incrementCount() {
count++; // Can modify outer's fields
}
// Access outer class reference
void showOuterReference() {
System.out.println("Outer this: " + OuterClass.this);
}
}
// Method that uses inner class
void demo() {
InnerClass inner = new InnerClass("demo-inner");
inner.display();
}
public int getCount() {
return count;
}
}
// Another example: linked list with node as inner class
class SimpleList<T> {
private Node head;
private int size = 0;
// Node is implementation detail - hide it
private class Node {
T data;
Node next;
Node(T data) {
this.data = data;
this.next = null;
}
}
public void add(T item) {
Node newNode = new Node(item);
if (head == null) {
head = newNode;
} else {
Node current = head;
while (current.next != null) {
current = current.next;
}
current.next = newNode;
}
size++; // Inner class indirectly affects outer
}
public void printAll() {
Node current = head;
while (current != null) {
System.out.print(current.data + " -> ");
Node next = current.next;
current = next;
}
System.out.println("null");
}
public int size() {
return size;
}
}
public class MemberInner {
public static void main(String[] args) {
System.out.println("=== Member Inner Class ===\n");
// Create outer class instance
OuterClass outer = new OuterClass("MyOuter");
// Create inner class instance
OuterClass.InnerClass inner = outer.new InnerClass("MyInner");
inner.display();
System.out.println("\n--- Modifying Outer from Inner ---");
System.out.println("Count before: " + outer.getCount());
inner.incrementCount();
inner.incrementCount();
System.out.println("Count after: " + outer.getCount());
System.out.println("\n--- Outer Reference ---");
inner.showOuterReference();
System.out.println("\n=== Linked List Example ===");
SimpleList<String> list = new SimpleList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
System.out.println("Size: " + list.size());
list.printAll();
// Node class is hidden - cannot access from here
// SimpleList.Node node = ...; // COMPILE ERROR!
System.out.println("\n=== Member Inner Class Rules ===");
System.out.println("""
1. Has access to ALL outer class members (including private)
2. Can modify outer class fields
3. Requires outer class instance to exist
4. Use OuterClass.this to reference outer instance
5. Create via: outer.new InnerClass()
6. From outside: OuterClass.InnerClass type name
""");
}
}
Member inner class can access all of outer's fields, even private ones.
Static nested class
A nested class that doesn't need an outer instance.
// Static Nested Class
class Calculator {
private static final double PI = 3.14159;
private double lastResult; // Instance field
Calculator() {
this.lastResult = 0;
}
// Static nested class
static class MathUtils {
// Can access outer's static members
static double circleArea(double radius) {
return PI * radius * radius; // Uses outer's PI
}
static double square(double n) {
return n * n;
}
static double cube(double n) {
return n * n * n;
}
// Cannot access outer's instance members
// void showLastResult() {
// System.out.println(lastResult); // ERROR!
// }
}
// Static nested class for configuration
static class Config {
private int precision;
private boolean useRadians;
Config() {
this.precision = 2;
this.useRadians = false;
}
Config(int precision, boolean useRadians) {
this.precision = precision;
this.useRadians = useRadians;
}
public int getPrecision() { return precision; }
public boolean isUseRadians() { return useRadians; }
@Override
public String toString() {
return "Config{precision=" + precision + ", radians=" + useRadians + "}";
}
}
public double calculate(double a, double b) {
lastResult = a + b;
return lastResult;
}
public double getLastResult() {
return lastResult;
}
}
// Builder pattern with static nested class
class Pizza {
private final String size;
private final String crust;
private final boolean cheese;
private final boolean pepperoni;
private final boolean mushrooms;
private final boolean onions;
// Private constructor - use builder
private Pizza(Builder builder) {
this.size = builder.size;
this.crust = builder.crust;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.mushrooms = builder.mushrooms;
this.onions = builder.onions;
}
// Static nested Builder class
static class Builder {
// Required parameters
private final String size;
// Optional parameters with defaults
private String crust = "regular";
private boolean cheese = true;
private boolean pepperoni = false;
private boolean mushrooms = false;
private boolean onions = false;
public Builder(String size) {
this.size = size;
}
public Builder crust(String crust) {
this.crust = crust;
return this; // Return this for chaining
}
public Builder pepperoni() {
this.pepperoni = true;
return this;
}
public Builder mushrooms() {
this.mushrooms = true;
return this;
}
public Builder onions() {
this.onions = true;
return this;
}
public Builder noCheese() {
this.cheese = false;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(size).append(" pizza, ").append(crust).append(" crust");
if (cheese) sb.append(", cheese");
if (pepperoni) sb.append(", pepperoni");
if (mushrooms) sb.append(", mushrooms");
if (onions) sb.append(", onions");
return sb.toString();
}
}
public class StaticNested {
public static void main(String[] args) {
System.out.println("=== Static Nested Class ===\n");
// Use static nested class directly
double area = Calculator.MathUtils.circleArea(5);
System.out.println("Circle area (r=5): " + area);
System.out.println("Square of 7: " + Calculator.MathUtils.square(7));
System.out.println("Cube of 3: " + Calculator.MathUtils.cube(3));
// Create instance of static nested class
Calculator.Config config = new Calculator.Config(4, true);
System.out.println("\nConfig: " + config);
// No outer instance needed!
Calculator.Config defaultConfig = new Calculator.Config();
System.out.println("Default config: " + defaultConfig);
System.out.println("\n=== Builder Pattern ===");
// Use builder to create Pizza
Pizza pizza1 = new Pizza.Builder("large")
.crust("thin")
.pepperoni()
.mushrooms()
.build();
System.out.println("Pizza 1: " + pizza1);
Pizza pizza2 = new Pizza.Builder("medium")
.pepperoni()
.onions()
.noCheese()
.build();
System.out.println("Pizza 2: " + pizza2);
Pizza pizza3 = new Pizza.Builder("small").build(); // Minimal
System.out.println("Pizza 3: " + pizza3);
System.out.println("\n=== Static vs Member Inner Class ===");
System.out.println("""
Static Nested Class:
- Declared with 'static' keyword
- Can only access outer's static members
- Does NOT need outer instance
- Create via: new Outer.Nested()
- Like a top-level class, just namespaced
Member Inner Class:
- No 'static' keyword
- Can access ALL outer members
- REQUIRES outer instance
- Create via: outer.new Inner()
When to use static nested:
- Builder pattern
- Helper classes that don't need outer state
- Grouping related classes
- Entry types for collections
""");
}
}
static class can only access outer's static members. No outer reference.
Local class
A class defined inside a method.
// Local Class (Class Inside Method)
import java.util.ArrayList;
import java.util.List;
class DataProcessor {
private String processorName;
DataProcessor(String name) {
this.processorName = name;
}
// Method with local class
public List<String> processData(List<String> data, String prefix) {
// Local class defined inside method
class DataTransformer {
private int count = 0;
// Can access method parameters (effectively final)
String transform(String item) {
count++;
// 'prefix' is effectively final - can use it
return prefix + ": " + item.toUpperCase() + " (#" + count + ")";
}
// Can access outer class members
String getProcessorInfo() {
return "Processor: " + processorName;
}
int getCount() {
return count;
}
}
// Use local class
DataTransformer transformer = new DataTransformer();
System.out.println(transformer.getProcessorInfo());
List<String> result = new ArrayList<>();
for (String item : data) {
result.add(transformer.transform(item));
}
System.out.println("Transformed " + transformer.getCount() + " items");
return result;
// DataTransformer not accessible outside this method!
}
// Another example: validator in method
public boolean validateAll(List<Integer> numbers, int minValue, int maxValue) {
// Local class for validation
class RangeValidator {
// Can access minValue, maxValue (effectively final)
boolean isValid(int num) {
return num >= minValue && num <= maxValue;
}
String getRange() {
return "[" + minValue + ", " + maxValue + "]";
}
}
RangeValidator validator = new RangeValidator();
System.out.println("Validating range: " + validator.getRange());
for (int num : numbers) {
if (!validator.isValid(num)) {
System.out.println("Invalid: " + num);
return false;
}
}
return true;
}
}
// Local class implementing interface
class EventSimulator {
interface EventHandler {
void handle(String event);
}
public void simulateEvents(List<String> events) {
// Local class implementing interface
class LoggingHandler implements EventHandler {
private int eventCount = 0;
@Override
public void handle(String event) {
eventCount++;
System.out.println("[Event " + eventCount + "] " + event);
}
public int getEventCount() {
return eventCount;
}
}
LoggingHandler handler = new LoggingHandler();
for (String event : events) {
handler.handle(event);
}
System.out.println("Total events handled: " + handler.getEventCount());
}
}
public class LocalClass {
public static void main(String[] args) {
System.out.println("=== Local Classes ===\n");
System.out.println("--- Data Processing ---");
DataProcessor processor = new DataProcessor("MainProcessor");
List<String> data = List.of("apple", "banana", "cherry");
List<String> processed = processor.processData(data, "ITEM");
System.out.println("\nProcessed data:");
for (String item : processed) {
System.out.println(" " + item);
}
System.out.println("\n--- Validation ---");
List<Integer> numbers = List.of(5, 10, 15, 20, 25);
int maxAllowed = ;
boolean valid = processor.validateAll(numbers, 1, maxAllowed);
System.out.println("All valid: " + valid);
List<Integer> badNumbers = List.of(5, 10, 50, 20); // 50 is out of range
valid = processor.validateAll(badNumbers, 1, maxAllowed);
System.out.println("All valid: " + valid);
System.out.println("\n--- Event Simulation ---");
EventSimulator simulator = new EventSimulator();
simulator.simulateEvents(List.of("START", "PROCESS", "COMPLETE"));
System.out.println("\n=== Local Class Rules ===");
System.out.println("""
1. Defined inside a method, constructor, or block
2. Scope limited to that block
3. Can access:
- Outer class members (all)
- Local variables that are effectively final
4. Cannot be static
5. Cannot have static members (except constants)
Effectively Final:
- Variable not modified after initialization
- Can use without 'final' keyword (Java 8+)
When to use:
- Helper class needed only in one method
- Encapsulate method-specific logic
- Alternative to anonymous class (when need name/multiple methods)
""");
}
}
// Local Class (Class Inside Method)
import java.util.ArrayList;
import java.util.List;
class DataProcessor {
private String processorName;
DataProcessor(String name) {
this.processorName = name;
}
// Method with local class
public List<String> processData(List<String> data, String prefix) {
// Local class defined inside method
class DataTransformer {
private int count = 0;
// Can access method parameters (effectively final)
String transform(String item) {
count++;
// 'prefix' is effectively final - can use it
return prefix + ": " + item.toUpperCase() + " (#" + count + ")";
}
// Can access outer class members
String getProcessorInfo() {
return "Processor: " + processorName;
}
int getCount() {
return count;
}
}
// Use local class
DataTransformer transformer = new DataTransformer();
System.out.println(transformer.getProcessorInfo());
List<String> result = new ArrayList<>();
for (String item : data) {
result.add(transformer.transform(item));
}
System.out.println("Transformed " + transformer.getCount() + " items");
return result;
// DataTransformer not accessible outside this method!
}
// Another example: validator in method
public boolean validateAll(List<Integer> numbers, int minValue, int maxValue) {
// Local class for validation
class RangeValidator {
// Can access minValue, maxValue (effectively final)
boolean isValid(int num) {
return num >= minValue && num <= maxValue;
}
String getRange() {
return "[" + minValue + ", " + maxValue + "]";
}
}
RangeValidator validator = new RangeValidator();
System.out.println("Validating range: " + validator.getRange());
for (int num : numbers) {
if (!validator.isValid(num)) {
System.out.println("Invalid: " + num);
return false;
}
}
return true;
}
}
// Local class implementing interface
class EventSimulator {
interface EventHandler {
void handle(String event);
}
public void simulateEvents(List<String> events) {
// Local class implementing interface
class LoggingHandler implements EventHandler {
private int eventCount = 0;
@Override
public void handle(String event) {
eventCount++;
System.out.println("[Event " + eventCount + "] " + event);
}
public int getEventCount() {
return eventCount;
}
}
LoggingHandler handler = new LoggingHandler();
for (String event : events) {
handler.handle(event);
}
System.out.println("Total events handled: " + handler.getEventCount());
}
}
public class LocalClass {
public static void main(String[] args) {
System.out.println("=== Local Classes ===\n");
System.out.println("--- Data Processing ---");
DataProcessor processor = new DataProcessor("MainProcessor");
List<String> data = List.of("apple", "banana", "cherry");
List<String> processed = processor.processData(data, "ITEM");
System.out.println("\nProcessed data:");
for (String item : processed) {
System.out.println(" " + item);
}
System.out.println("\n--- Validation ---");
List<Integer> numbers = List.of(5, 10, 15, 20, 25);
int maxAllowed = ;
boolean valid = processor.validateAll(numbers, 1, maxAllowed);
System.out.println("All valid: " + valid);
List<Integer> badNumbers = List.of(5, 10, 50, 20); // 50 is out of range
valid = processor.validateAll(badNumbers, 1, maxAllowed);
System.out.println("All valid: " + valid);
System.out.println("\n--- Event Simulation ---");
EventSimulator simulator = new EventSimulator();
simulator.simulateEvents(List.of("START", "PROCESS", "COMPLETE"));
System.out.println("\n=== Local Class Rules ===");
System.out.println("""
1. Defined inside a method, constructor, or block
2. Scope limited to that block
3. Can access:
- Outer class members (all)
- Local variables that are effectively final
4. Cannot be static
5. Cannot have static members (except constants)
Effectively Final:
- Variable not modified after initialization
- Can use without 'final' keyword (Java 8+)
When to use:
- Helper class needed only in one method
- Encapsulate method-specific logic
- Alternative to anonymous class (when need name/multiple methods)
""");
}
}
Local classes exist only within the method. Can access final/effectively final locals.
Anonymous class
One-time class definition and instantiation.
// Anonymous Class
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
interface Greeting {
void greet(String name);
}
abstract class Animal {
abstract void makeSound();
void sleep() {
System.out.println("Zzz...");
}
}
class SortingDemo {
// Using anonymous class for Comparator
public void sortStrings(List<String> strings) {
// Anonymous class implementing Comparator
Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
Collections.sort(strings, lengthComparator);
}
// Anonymous class inline
public void sortByLastChar(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
char last1 = s1.charAt(s1.length() - 1);
char last2 = s2.charAt(s2.length() - 1);
return Character.compare(last1, last2);
}
});
}
}
class ButtonSimulator {
interface ClickListener {
void onClick();
}
private ClickListener listener;
public void setOnClickListener(ClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) {
listener.onClick();
}
}
}
public class AnonymousClass {
public static void main(String[] args) {
System.out.println("=== Anonymous Classes ===\n");
// Anonymous class implementing interface
System.out.println("--- Implementing Interface ---");
Greeting formalGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Good day, " + name + ". How do you do?");
}
};
Greeting casualGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hey " + name + "! What's up?");
}
};
formalGreeting.greet("Sir");
casualGreeting.greet("buddy");
// Anonymous class extending abstract class
System.out.println("\n--- Extending Abstract Class ---");
Animal dog = new Animal() {
@Override
void makeSound() {
System.out.println("Woof woof!");
}
};
Animal cat = new Animal() {
@Override
void makeSound() {
System.out.println("Meow!");
}
// Can add new methods, but can't call via Animal reference
void purr() {
System.out.println("Purrrr...");
}
};
dog.makeSound();
dog.sleep(); // Inherited method
cat.makeSound();
// cat.purr(); // Cannot call - Animal doesn't have purr()
// Sorting with anonymous Comparator
System.out.println("\n--- Sorting with Anonymous Comparator ---");
List<String> words = new ArrayList<>(List.of("cat", "elephant", "dog", "butterfly"));
System.out.println("Original: " + words);
SortingDemo sorter = new SortingDemo();
sorter.sortStrings(words);
System.out.println("By length: " + words);
words = new ArrayList<>(List.of("cat", "elephant", "dog", "butterfly"));
sorter.sortByLastChar(words);
System.out.println("By last char: " + words);
// Event listener pattern
System.out.println("\n--- Event Listener Pattern ---");
ButtonSimulator button = new ButtonSimulator();
button.setOnClickListener(new ButtonSimulator.ClickListener() {
private int clickCount = 0; // Anonymous class can have state!
@Override
public void onClick() {
clickCount++;
System.out.println("Button clicked! Count: " + clickCount);
}
});
button.click();
button.click();
button.click();
// Capturing local variables
System.out.println("\n--- Capturing Local Variables ---");
String message = "Hello"; // Effectively final
int times = ;
Runnable printer = new Runnable() {
@Override
public void run() {
for (int i = 0; i < times; i++) {
System.out.println(message);
}
}
};
printer.run();
// message = "Changed"; // Would cause error - breaks effectively final
System.out.println("\n=== Anonymous Class Rules ===");
System.out.println("""
1. No name - defined and instantiated in one expression
2. Implements interface OR extends class (not both)
3. Must end with semicolon after closing brace
4. Cannot have explicit constructor
5. Can have instance fields and methods
6. Can capture effectively final local variables
Syntax:
new Interface() { ... };
new ClassName(args) { ... };
When to use:
- One-time implementation needed
- Simple interface (1-2 methods)
- Event listeners/callbacks
Modern alternative: Lambda expressions (for single-method interfaces)
""");
}
}
// Anonymous Class
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
interface Greeting {
void greet(String name);
}
abstract class Animal {
abstract void makeSound();
void sleep() {
System.out.println("Zzz...");
}
}
class SortingDemo {
// Using anonymous class for Comparator
public void sortStrings(List<String> strings) {
// Anonymous class implementing Comparator
Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
Collections.sort(strings, lengthComparator);
}
// Anonymous class inline
public void sortByLastChar(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
char last1 = s1.charAt(s1.length() - 1);
char last2 = s2.charAt(s2.length() - 1);
return Character.compare(last1, last2);
}
});
}
}
class ButtonSimulator {
interface ClickListener {
void onClick();
}
private ClickListener listener;
public void setOnClickListener(ClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) {
listener.onClick();
}
}
}
public class AnonymousClass {
public static void main(String[] args) {
System.out.println("=== Anonymous Classes ===\n");
// Anonymous class implementing interface
System.out.println("--- Implementing Interface ---");
Greeting formalGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Good day, " + name + ". How do you do?");
}
};
Greeting casualGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hey " + name + "! What's up?");
}
};
formalGreeting.greet("Sir");
casualGreeting.greet("buddy");
// Anonymous class extending abstract class
System.out.println("\n--- Extending Abstract Class ---");
Animal dog = new Animal() {
@Override
void makeSound() {
System.out.println("Woof woof!");
}
};
Animal cat = new Animal() {
@Override
void makeSound() {
System.out.println("Meow!");
}
// Can add new methods, but can't call via Animal reference
void purr() {
System.out.println("Purrrr...");
}
};
dog.makeSound();
dog.sleep(); // Inherited method
cat.makeSound();
// cat.purr(); // Cannot call - Animal doesn't have purr()
// Sorting with anonymous Comparator
System.out.println("\n--- Sorting with Anonymous Comparator ---");
List<String> words = new ArrayList<>(List.of("cat", "elephant", "dog", "butterfly"));
System.out.println("Original: " + words);
SortingDemo sorter = new SortingDemo();
sorter.sortStrings(words);
System.out.println("By length: " + words);
words = new ArrayList<>(List.of("cat", "elephant", "dog", "butterfly"));
sorter.sortByLastChar(words);
System.out.println("By last char: " + words);
// Event listener pattern
System.out.println("\n--- Event Listener Pattern ---");
ButtonSimulator button = new ButtonSimulator();
button.setOnClickListener(new ButtonSimulator.ClickListener() {
private int clickCount = 0; // Anonymous class can have state!
@Override
public void onClick() {
clickCount++;
System.out.println("Button clicked! Count: " + clickCount);
}
});
button.click();
button.click();
button.click();
// Capturing local variables
System.out.println("\n--- Capturing Local Variables ---");
String message = "Hello"; // Effectively final
int times = ;
Runnable printer = new Runnable() {
@Override
public void run() {
for (int i = 0; i < times; i++) {
System.out.println(message);
}
}
};
printer.run();
// message = "Changed"; // Would cause error - breaks effectively final
System.out.println("\n=== Anonymous Class Rules ===");
System.out.println("""
1. No name - defined and instantiated in one expression
2. Implements interface OR extends class (not both)
3. Must end with semicolon after closing brace
4. Cannot have explicit constructor
5. Can have instance fields and methods
6. Can capture effectively final local variables
Syntax:
new Interface() { ... };
new ClassName(args) { ... };
When to use:
- One-time implementation needed
- Simple interface (1-2 methods)
- Event listeners/callbacks
Modern alternative: Lambda expressions (for single-method interfaces)
""");
}
}
// Anonymous Class
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
interface Greeting {
void greet(String name);
}
abstract class Animal {
abstract void makeSound();
void sleep() {
System.out.println("Zzz...");
}
}
class SortingDemo {
// Using anonymous class for Comparator
public void sortStrings(List<String> strings) {
// Anonymous class implementing Comparator
Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
Collections.sort(strings, lengthComparator);
}
// Anonymous class inline
public void sortByLastChar(List<String> strings) {
Collections.sort(strings, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
char last1 = s1.charAt(s1.length() - 1);
char last2 = s2.charAt(s2.length() - 1);
return Character.compare(last1, last2);
}
});
}
}
class ButtonSimulator {
interface ClickListener {
void onClick();
}
private ClickListener listener;
public void setOnClickListener(ClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) {
listener.onClick();
}
}
}
public class AnonymousClass {
public static void main(String[] args) {
System.out.println("=== Anonymous Classes ===\n");
// Anonymous class implementing interface
System.out.println("--- Implementing Interface ---");
Greeting formalGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Good day, " + name + ". How do you do?");
}
};
Greeting casualGreeting = new Greeting() {
@Override
public void greet(String name) {
System.out.println("Hey " + name + "! What's up?");
}
};
formalGreeting.greet("Sir");
casualGreeting.greet("buddy");
// Anonymous class extending abstract class
System.out.println("\n--- Extending Abstract Class ---");
Animal dog = new Animal() {
@Override
void makeSound() {
System.out.println("Woof woof!");
}
};
Animal cat = new Animal() {
@Override
void makeSound() {
System.out.println("Meow!");
}
// Can add new methods, but can't call via Animal reference
void purr() {
System.out.println("Purrrr...");
}
};
dog.makeSound();
dog.sleep(); // Inherited method
cat.makeSound();
// cat.purr(); // Cannot call - Animal doesn't have purr()
// Sorting with anonymous Comparator
System.out.println("\n--- Sorting with Anonymous Comparator ---");
List<String> words = new ArrayList<>(List.of("cat", "elephant", "dog", "butterfly"));
System.out.println("Original: " + words);
SortingDemo sorter = new SortingDemo();
sorter.sortStrings(words);
System.out.println("By length: " + words);
words = new ArrayList<>(List.of("cat", "elephant", "dog", "butterfly"));
sorter.sortByLastChar(words);
System.out.println("By last char: " + words);
// Event listener pattern
System.out.println("\n--- Event Listener Pattern ---");
ButtonSimulator button = new ButtonSimulator();
button.setOnClickListener(new ButtonSimulator.ClickListener() {
private int clickCount = 0; // Anonymous class can have state!
@Override
public void onClick() {
clickCount++;
System.out.println("Button clicked! Count: " + clickCount);
}
});
button.click();
button.click();
button.click();
// Capturing local variables
System.out.println("\n--- Capturing Local Variables ---");
String message = "Hello"; // Effectively final
int times = ;
Runnable printer = new Runnable() {
@Override
public void run() {
for (int i = 0; i < times; i++) {
System.out.println(message);
}
}
};
printer.run();
// message = "Changed"; // Would cause error - breaks effectively final
System.out.println("\n=== Anonymous Class Rules ===");
System.out.println("""
1. No name - defined and instantiated in one expression
2. Implements interface OR extends class (not both)
3. Must end with semicolon after closing brace
4. Cannot have explicit constructor
5. Can have instance fields and methods
6. Can capture effectively final local variables
Syntax:
new Interface() { ... };
new ClassName(args) { ... };
When to use:
- One-time implementation needed
- Simple interface (1-2 methods)
- Event listeners/callbacks
Modern alternative: Lambda expressions (for single-method interfaces)
""");
}
}
new Interface() { } creates anonymous implementation. Common for callbacks.
Common patterns
Where inner classes shine.
// Common Inner Class Patterns
import java.util.Iterator;
import java.util.NoSuchElementException;
// Pattern 1: Iterator pattern
class NumberRange implements Iterable<Integer> {
private final int start;
private final int end;
NumberRange(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
return new RangeIterator();
}
// Private inner class for iterator
private class RangeIterator implements Iterator<Integer> {
private int current = start; // Uses outer's start
@Override
public boolean hasNext() {
return current <= end; // Uses outer's end
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return current++;
}
}
}
// Pattern 2: Callback/Handler with state
class TaskRunner {
interface TaskCallback {
void onProgress(int percent);
void onComplete(String result);
void onError(String error);
}
public void runTask(String taskName, TaskCallback callback) {
System.out.println("Starting task: " + taskName);
// Simulate progress
for (int i = 0; i <= 100; i += 25) {
callback.onProgress(i);
}
callback.onComplete("Task completed successfully!");
}
}
// Pattern 3: Type-safe constants with behavior
class Operation {
private final String symbol;
private Operation(String symbol) {
this.symbol = symbol;
}
public double apply(double a, double b) {
throw new UnsupportedOperationException();
}
// Anonymous classes as constants
public static final Operation ADD = new Operation("+") {
@Override
public double apply(double a, double b) {
return a + b;
}
};
public static final Operation SUBTRACT = new Operation("-") {
@Override
public double apply(double a, double b) {
return a - b;
}
};
public static final Operation MULTIPLY = new Operation("*") {
@Override
public double apply(double a, double b) {
return a * b;
}
};
public static final Operation DIVIDE = new Operation("/") {
@Override
public double apply(double a, double b) {
if (b == 0) throw new ArithmeticException("Division by zero");
return a / b;
}
};
@Override
public String toString() {
return symbol;
}
}
// Pattern 4: Fluent builder with inner class
class HttpRequest {
private final String method;
private final String url;
private final String body;
private final java.util.Map<String, String> headers;
private HttpRequest(Builder builder) {
this.method = builder.method;
this.url = builder.url;
this.body = builder.body;
this.headers = new java.util.HashMap<>(builder.headers);
}
// Static inner builder
public static class Builder {
private String method = "GET";
private String url;
private String body = "";
private java.util.Map<String, String> headers = new java.util.HashMap<>();
public Builder url(String url) {
this.url = url;
return this;
}
public Builder get() {
this.method = "GET";
return this;
}
public Builder post(String body) {
this.method = "POST";
this.body = body;
return this;
}
public Builder header(String key, String value) {
this.headers.put(key, value);
return this;
}
public HttpRequest build() {
if (url == null) throw new IllegalStateException("URL required");
return new HttpRequest(this);
}
}
public void send() {
System.out.println(method + " " + url);
headers.forEach((k, v) -> System.out.println(" " + k + ": " + v));
if (!body.isEmpty()) {
System.out.println(" Body: " + body);
}
}
}
// Pattern 5: Composite with member inner class
class FileSystem {
// Member inner class for entries
class Entry {
private String name;
private boolean isDirectory;
private java.util.List<Entry> children;
Entry(String name, boolean isDirectory) {
this.name = name;
this.isDirectory = isDirectory;
this.children = isDirectory ? new java.util.ArrayList<>() : null;
}
public Entry addChild(String name, boolean isDirectory) {
if (!this.isDirectory) {
throw new IllegalStateException("Cannot add child to file");
}
Entry child = new Entry(name, isDirectory); // Uses outer.new Entry()
children.add(child);
return child;
}
public void print(String indent) {
System.out.println(indent + (isDirectory ? "📁 " : "📄 ") + name);
if (children != null) {
for (Entry child : children) {
child.print(indent + " ");
}
}
}
}
private Entry root;
public FileSystem(String rootName) {
this.root = new Entry(rootName, true);
}
public Entry getRoot() {
return root;
}
}
public class InnerClassPatterns {
public static void main(String[] args) {
System.out.println("=== Inner Class Patterns ===\n");
// Pattern 1: Iterator
System.out.println("--- Iterator Pattern ---");
NumberRange range = new NumberRange(1, 5);
for (int num : range) {
System.out.print(num + " ");
}
System.out.println();
// Pattern 2: Callback
System.out.println("\n--- Callback Pattern ---");
TaskRunner runner = new TaskRunner();
runner.runTask("Download", new TaskRunner.TaskCallback() {
@Override
public void onProgress(int percent) {
System.out.println("Progress: " + percent + "%");
}
@Override
public void onComplete(String result) {
System.out.println("Done: " + result);
}
@Override
public void onError(String error) {
System.out.println("Error: " + error);
}
});
// Pattern 3: Operation constants
System.out.println("\n--- Operation Pattern ---");
double a = 10, b = 3;
System.out.println(a + " " + Operation.ADD + " " + b + " = " + Operation.ADD.apply(a, b));
System.out.println(a + " " + Operation.MULTIPLY + " " + b + " = " + Operation.MULTIPLY.apply(a, b));
// Pattern 4: Fluent Builder
System.out.println("\n--- Fluent Builder Pattern ---");
HttpRequest request = new HttpRequest.Builder()
.url("https://api.example.com/users")
.post("{\"name\": \"John\"}")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.build();
request.send();
// Pattern 5: Composite
System.out.println("\n--- Composite Pattern ---");
FileSystem fs = new FileSystem("root");
FileSystem.Entry src = fs.getRoot().addChild("src", true);
src.addChild("Main.java", false);
src.addChild("Utils.java", false);
FileSystem.Entry test = fs.getRoot().addChild("test", true);
test.addChild("MainTest.java", false);
fs.getRoot().addChild("README.md", false);
fs.getRoot().print("");
System.out.println("\n=== Pattern Summary ===");
System.out.println("""
1. Iterator: Private inner class hides implementation
2. Callback: Anonymous class for event handling
3. Constants: Anonymous classes for behavior-rich constants
4. Builder: Static nested class for construction
5. Composite: Member inner for tree-like structures
""");
}
}
Iterators, builders, event handlers - all classic inner class uses.
Exercise: Practical.java
Build a data structure with inner helper classes