Why the Iterator Pattern Is the Most Used Yet Unnoticed Design Pattern in Java
The article explains how the Iterator pattern, built into Java as Iterable and Iterator interfaces, lets you traverse any collection uniformly without exposing its internal structure, demonstrates the fail‑fast mechanism, and shows when custom collections should implement Iterable.
1. The problem: traversal logic tangled with collection internals
When a custom collection such as OrderList stores orders in an array, client code must know the internal representation and iterate with index loops, exposing the array and coupling traversal to the storage type. If the internal structure later changes to a linked list, all client loops break.
public class OrderList {
private Order[] orders = new Order[100];
private int size = 0;
public void add(Order o) { orders[size++] = o; }
public Order[] getOrders() { return orders; }
public int getSize() { return size; }
}
OrderList list = ...;
Order[] arr = list.getOrders();
for (int i = 0; i < list.getSize(); i++) {
Order o = arr[i];
// ...
}The two main issues are:
Exposing internal structure : callers must know that OrderList uses an array and can even modify it directly via getOrders().
Traversal tied to storage : switching to a linked list would require rewriting every index‑based loop.
The goal of the Iterator pattern is to hide these details by providing an iterator object that encapsulates the traversal logic.
2. Iterator pattern: separating "how to traverse"
The pattern defines an iterator interface (typically hasNext() and next()) and lets the collection return an iterator instance.
public interface Iterator<T> {
boolean hasNext();
T next();
}Step 1: Implement Iterable in the collection and return a concrete iterator.
public class OrderList implements Iterable<Order> {
private Order[] orders = new Order[100];
private int size = 0;
public void add(Order o) { orders[size++] = o; }
public Iterator<Order> iterator() {
return new Iterator<Order>() {
private int cursor = 0;
public boolean hasNext() { return cursor < size; }
public Order next() { return orders[cursor++]; }
};
}
}Step 2: Client code uses only the iterator, never touching the internal array.
OrderList list = ...;
Iterator<Order> it = list.iterator();
while (it.hasNext()) {
Order o = it.next();
// ...
}Compared with the original index loop, the client no longer knows whether OrderList is backed by an array or a linked list; changing the internal structure only requires updating the iterator implementation.
3. Java’s built‑in iterator: the truth behind for‑each
Java provides two core interfaces: Iterable<T>: a collection that can produce an iterator via iterator(). All standard collections (List, Set, Queue, …) implement it. Iterator<T>: the iterator itself, offering hasNext(), next(), and optionally remove().
The enhanced for‑loop ( for (Order o : orders) { … }) is merely syntactic sugar that the compiler translates into iterator usage:
Iterator<Order> it = orders.iterator();
while (it.hasNext()) {
Order o = it.next();
// ...
}If a custom class implements Iterable, it can be traversed with the same for‑each syntax.
4. The fail‑fast mechanism
Modifying a collection while iterating with a for‑each loop triggers ConcurrentModificationException. The iterator records the collection’s modCount at creation; each next() checks whether modCount has changed. If it has, the iterator throws the exception.
List<Order> orders = new ArrayList<>();
for (Order o : orders) {
if (o.isCancelled()) {
orders.remove(o); // throws ConcurrentModificationException
}
}Correct removal uses the iterator’s own remove() method, which updates the recorded modCount and avoids the exception:
Iterator<Order> it = orders.iterator();
while (it.hasNext()) {
Order o = it.next();
if (o.isCancelled()) {
it.remove(); // safe
}
}
// or Java 8+: orders.removeIf(Order::isCancelled);The fail‑fast behavior is intentional: it quickly exposes concurrent modification bugs instead of producing corrupted traversal results.
5. When to apply the Iterator pattern
Signals that the pattern is appropriate:
You have a custom collection and want a uniform, elegant traversal (especially for‑each support). Implement Iterable.
You need to expose traversal without revealing internal storage.
You want multiple traversal strategies (forward, reverse, filtered) via different iterator implementations.
Signals that it is unnecessary:
You are using standard Java collections, which already provide iterators.
A simple array with index loops suffices.
In practice, you rarely need to write a full iterator from scratch; simply making a custom collection implement Iterable gives you free for‑each support and the fail‑fast safety of the built‑in iterator.
6. Summary
The Iterator pattern offers a unified way to traverse collections without exposing their internal structures. Java embeds this pattern in the Iterable and Iterator interfaces, and the enhanced for‑each loop is just syntactic sugar for iterator usage. Understanding the iterator’s cursor, the modCount fail‑fast mechanism, and when to implement Iterable helps you write cleaner, more robust Java code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
