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

Problem

Clients need to traverse different collection types uniformly without knowing internal structure.

Solution

  interface Iterator<T> {
    boolean hasNext();
    T next();
}

interface Iterable<T> {
    Iterator<T> iterator();
}

class BookShelf implements Iterable<String> {
    private final List<String> books = new ArrayList<>();

    void addBook(String title) { books.add(title); }

    @Override
    public Iterator<String> iterator() {
        return books.iterator(); // delegates to ArrayList iterator
    }
}

// Usage
BookShelf shelf = new BookShelf();
shelf.addBook("Clean Code");
shelf.addBook("Effective Java");

for (String book : shelf) {  // uses iterator() internally
    System.out.println(book);
}
  

Java Iterator Interface

  Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.isEmpty()) {
        it.remove(); // safe removal during iteration
    }
}
  

Iterable and Enhanced For-Loop

Any class implementing Iterable<T> works with enhanced for-loop:

  public class Range implements Iterable<Integer> {
    private final int start, end;

    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            int current = start;
            public boolean hasNext() { return current <= end; }
            public Integer next() { return current++; }
        };
    }
}

for (int i : new Range(1, 5)) {
    System.out.println(i); // 1, 2, 3, 4, 5
}
  

Java Stream Iterator

  Iterator<String> it = list.stream()
    .filter(s -> !s.isEmpty())
    .map(String::toUpperCase)
    .iterator();
  

Custom Collection Iterator

  class BinaryTree<T> implements Iterable<T> {
    private Node<T> root;

    public Iterator<T> iterator() {
        return new InOrderIterator(root);
    }

    private class InOrderIterator implements Iterator<T> {
        private final Deque<Node<T>> stack = new ArrayDeque<>();

        InOrderIterator(Node<T> root) {
            pushLeft(root);
        }

        public boolean hasNext() { return !stack.isEmpty(); }

        public T next() {
            Node<T> node = stack.pop();
            pushLeft(node.right);
            return node.data;
        }

        private void pushLeft(Node<T> node) {
            while (node != null) {
                stack.push(node);
                node = node.left;
            }
        }
    }
}
  

Fail-Fast vs Fail-Safe

Type Behavior Example
Fail-fast Throws ConcurrentModificationException ArrayList.iterator()
Fail-safe Works on a snapshot CopyOnWriteArrayList.iterator()

When to Use

  • Access collection elements without exposing internal structure
  • Support multiple traversal methods (forward, backward, filtered)
  • Provide a uniform interface for different collection types

Best Practices

  • Implement Iterable to support enhanced for-loops
  • Use Iterator.remove() for safe removal during iteration
  • Prefer Stream API for functional traversal with filtering and mapping
  • Return read-only iterators when the collection should not be modified