Skip to main content

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

  1. Single Responsibility Principle - Collection and iteration are separate
  2. Encapsulation - Hides internal structure of collection
  3. 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

  1. Encapsulation - Hide internal structure of collection
  2. Single Responsibility - Separate traversal from collection
  3. Multiple Iterators - Multiple simultaneous traversals
  4. Uniform Interface - Same interface for different collections
  5. Flexibility - Different traversal algorithms

Disadvantages

  1. Overhead - Extra classes for simple collections
  2. Complexity - More code than direct access
  3. 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;
}
}
  • Composite - Iterators often used with Composite
  • Factory Method - Create iterator instances
  • Memento - Store iteration state
  • Visitor - Iterate to apply operations

Key Takeaways

  1. Separate iteration from collection
  2. Hide internal structure
  3. Provide uniform traversal interface
  4. Support multiple simultaneous iterations
  5. Foundation of Java Collections Framework
  6. External iterator = client controls
  7. Internal iterator = collection controls

The Iterator Pattern provides a way to access elements of a collection sequentially without exposing its underlying representation!