Iterator Pattern
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Problem
You need to traverse a collection of objects without exposing the internal structure of the collection. Different collections may need different traversal algorithms.
Common Scenarios:
- Iterating through different data structures (lists, trees, graphs)
- Multiple traversal algorithms for same collection
- Providing uniform interface for different collections
- Implementing custom collection classes
Design Principles Applied
- Single Responsibility Principle - Collection and iteration are separate
- Encapsulation - Hides internal structure of collection
- Program to an interface - Iterator interface, not concrete implementation
UML Diagram
Implementation
Example: Restaurant Menu Iterator
Step 1: Iterator Interface
// Iterator interface
public interface Iterator<T> {
boolean hasNext();
T next();
}
Step 2: Aggregate Interface
// Aggregate interface
public interface Menu {
Iterator<MenuItem> createIterator();
}
Step 3: Item Class
public class MenuItem {
private String name;
private String description;
private boolean vegetarian;
private double price;
public MenuItem(String name, String description,
boolean vegetarian, double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName() { return name; }
public String getDescription() { return description; }
public double getPrice() { return price; }
public boolean isVegetarian() { return vegetarian; }
@Override
public String toString() {
return name + ", $" + price + "\n " + description;
}
}
Step 4: Concrete Aggregate with Array
// Pancake House Menu uses Array
public class PancakeHouseMenu implements Menu {
private static final int MAX_ITEMS = 6;
private int numberOfItems = 0;
private MenuItem[] menuItems;
public PancakeHouseMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("K&B's Pancake Breakfast",
"Pancakes with scrambled eggs and toast", true, 2.99);
addItem("Regular Pancake Breakfast",
"Pancakes with fried eggs, sausage", false, 2.99);
addItem("Blueberry Pancakes",
"Pancakes made with fresh blueberries", true, 3.49);
addItem("Waffles",
"Waffles with your choice of blueberries or strawberries", true, 3.59);
}
public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.err.println("Sorry, menu is full!");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
@Override
public Iterator<MenuItem> createIterator() {
return new PancakeHouseMenuIterator(menuItems);
}
}
// Array iterator
public class PancakeHouseMenuIterator implements Iterator<MenuItem> {
private MenuItem[] items;
private int position = 0;
public PancakeHouseMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public MenuItem next() {
MenuItem menuItem = items[position];
position++;
return menuItem;
}
@Override
public boolean hasNext() {
return position < items.length && items[position] != null;
}
}
Step 5: Concrete Aggregate with ArrayList
// Diner Menu uses ArrayList
public class DinerMenu implements Menu {
private List<MenuItem> menuItems;
public DinerMenu() {
menuItems = new ArrayList<>();
addItem("Vegetarian BLT",
"(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
addItem("BLT",
"Bacon with lettuce & tomato on whole wheat", false, 2.99);
addItem("Soup of the day",
"Soup of the day, with a side of potato salad", false, 3.29);
addItem("Hotdog",
"A hot dog, with sauerkraut, relish, onions", false, 3.05);
}
public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}
@Override
public Iterator<MenuItem> createIterator() {
return new DinerMenuIterator(menuItems);
}
}
// ArrayList iterator
public class DinerMenuIterator implements Iterator<MenuItem> {
private List<MenuItem> items;
private int position = 0;
public DinerMenuIterator(List<MenuItem> items) {
this.items = items;
}
@Override
public MenuItem next() {
MenuItem menuItem = items.get(position);
position++;
return menuItem;
}
@Override
public boolean hasNext() {
return position < items.size();
}
}
Step 6: Client (Waitress)
public class Waitress {
private Menu pancakeHouseMenu;
private Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator<MenuItem> pancakeIterator = pancakeHouseMenu.createIterator();
Iterator<MenuItem> dinerIterator = dinerMenu.createIterator();
System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);
System.out.println("\nLUNCH");
printMenu(dinerIterator);
}
private void printMenu(Iterator<MenuItem> iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
public void printVegetarianMenu() {
System.out.println("\nVEGETARIAN MENU\n---------------");
printVegetarianMenu(pancakeHouseMenu.createIterator());
printVegetarianMenu(dinerMenu.createIterator());
}
private void printVegetarianMenu(Iterator<MenuItem> iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
if (menuItem.isVegetarian()) {
System.out.print(menuItem.getName() + ", ");
System.out.println(menuItem.getPrice());
}
}
}
}
Step 7: Test Client
public class MenuTestDrive {
public static void main(String[] args) {
Menu pancakeHouseMenu = new PancakeHouseMenu();
Menu dinerMenu = new DinerMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);
waitress.printMenu();
waitress.printVegetarianMenu();
}
}
Java Iterator Interface
Java provides built-in iterator support:
import java.util.Iterator;
public class DinerMenu implements Menu {
private List<MenuItem> menuItems;
@Override
public Iterator<MenuItem> createIterator() {
return menuItems.iterator(); // Use built-in iterator
}
}
Enhanced For Loop
// Using Java's Iterable interface
public class DinerMenu implements Menu, Iterable<MenuItem> {
private List<MenuItem> menuItems;
@Override
public Iterator<MenuItem> iterator() {
return menuItems.iterator();
}
}
// Client can now use enhanced for loop
DinerMenu menu = new DinerMenu();
for (MenuItem item : menu) {
System.out.println(item);
}
Real-World Examples
Example 2: Binary Tree Iterator
class TreeNode {
int value;
TreeNode left, right;
TreeNode(int value) {
this.value = value;
}
}
public class BinaryTreeIterator implements Iterator<Integer> {
private Stack<TreeNode> stack;
public BinaryTreeIterator(TreeNode root) {
stack = new Stack<>();
pushLeft(root);
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public Integer next() {
TreeNode node = stack.pop();
pushLeft(node.right);
return node.value;
}
private void pushLeft(TreeNode node) {
while (node != null) {
stack.push(node);
node = node.left;
}
}
}
// Usage
TreeNode root = new TreeNode(4);
root.left = new TreeNode(2);
root.right = new TreeNode(6);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(3);
BinaryTreeIterator iterator = new BinaryTreeIterator(root);
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
// Output: 1 2 3 4 6
Example 3: Filtered Iterator
public class FilterIterator<T> implements Iterator<T> {
private Iterator<T> iterator;
private Predicate<T> predicate;
private T next;
private boolean hasNext;
public FilterIterator(Iterator<T> iterator, Predicate<T> predicate) {
this.iterator = iterator;
this.predicate = predicate;
findNext();
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public T next() {
T result = next;
findNext();
return result;
}
private void findNext() {
hasNext = false;
while (iterator.hasNext()) {
next = iterator.next();
if (predicate.test(next)) {
hasNext = true;
return;
}
}
}
}
// Usage
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Iterator<Integer> evenIterator = new FilterIterator<>(
numbers.iterator(),
n -> n % 2 == 0
);
while (evenIterator.hasNext()) {
System.out.println(evenIterator.next());
}
// Output: 2 4 6
Internal vs External Iterators
External Iterator (Active)
Client controls iteration:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
Internal Iterator (Passive)
Collection controls iteration:
list.forEach(item -> System.out.println(item));
Java Collections Framework
The Iterator pattern is fundamental to Java Collections:
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
// Enhanced for loop (uses iterator internally)
for (String item : list) {
System.out.println(item);
}
// Stream API (internal iterator)
list.stream().forEach(System.out::println);
Advantages
- Encapsulation - Hide internal structure of collection
- Single Responsibility - Separate traversal from collection
- Multiple Iterators - Multiple simultaneous traversals
- Uniform Interface - Same interface for different collections
- Flexibility - Different traversal algorithms
Disadvantages
- Overhead - Extra classes for simple collections
- Complexity - More code than direct access
- Performance - Slight overhead compared to direct iteration
When to Use
✅ Use Iterator Pattern When:
- You need to access collection elements without exposing internals
- You need multiple traversal algorithms
- You want uniform interface for different collections
- You need concurrent iteration
❌ Don't Use Iterator Pattern When:
- Simple array with known structure
- Single traversal method is sufficient
- Performance is critical and overhead matters
Iterator with Remove
public interface Iterator<T> {
boolean hasNext();
T next();
void remove(); // Optional operation
}
public class ArrayIterator<T> implements Iterator<T> {
private T[] array;
private int position = 0;
private boolean canRemove = false;
@Override
public void remove() {
if (!canRemove) {
throw new IllegalStateException("next() must be called before remove()");
}
// Remove logic here
canRemove = false;
}
@Override
public T next() {
T item = array[position++];
canRemove = true;
return item;
}
}
Related Patterns
- Composite - Iterators often used with Composite
- Factory Method - Create iterator instances
- Memento - Store iteration state
- Visitor - Iterate to apply operations
Key Takeaways
- Separate iteration from collection
- Hide internal structure
- Provide uniform traversal interface
- Support multiple simultaneous iterations
- Foundation of Java Collections Framework
- External iterator = client controls
- Internal iterator = collection controls
The Iterator Pattern provides a way to access elements of a collection sequentially without exposing its underlying representation!