What is the Iterator Pattern?
The Iterator Design Pattern provides a way to access the elements of a collection sequentially without exposing the underlying representation (e.g., list, stack, tree, etc.).
It falls under the Behavioral Design Patterns category.
Provide a standard way to traverse through a collection of objects one at a time without exposing the internal structure of the collection.
Components of the Iterator Pattern
- Iterator: Interface that defines the methods for traversing.
- Concrete Iterator: Implements the
Iterator
interface. - Aggregate (Collection): Interface that defines a method to create an iterator.
- Concrete Aggregate: A class that implements the
Aggregate
interface and returns an iterator.
Real-World Analogy
Think of a TV remote (iterator) that lets you go through TV channels (collection) without having to know how the TV stores the channel list internally.
Java’s Built-In Iterator
Java already provides the Iterator<E>
interface in the java.util
package with these methods:
boolean hasNext(); E next(); void remove(); // optional
But let’s learn how to implement the pattern from scratch to understand it deeply.
Custom Implementation of the Iterator Pattern
Step 1: Create the Iterator Interface
public interface MyIterator<T> { boolean hasNext(); T next(); }
Step 2: Create the Aggregate Interface
public interface MyCollection<T> { MyIterator<T> createIterator(); }
Step 3: Create the Concrete Collection
import java.util.ArrayList; import java.util.List; public class NameCollection implements MyCollection<String> { private List<String> names = new ArrayList<>(); public void addName(String name) { names.add(name); } public String getName(int index) { return names.get(index); } public int size() { return names.size(); } @Override public MyIterator<String> createIterator() { return new NameIterator(this); } }
Step 4: Create the Concrete Iterator
public class NameIterator implements MyIterator<String> { private NameCollection collection; private int index = 0; public NameIterator(NameCollection collection) { this.collection = collection; } @Override public boolean hasNext() { return index < collection.size(); } @Override public String next() { return collection.getName(index++); } }
Step 5: Client Code (Test)
public class IteratorPatternDemo { public static void main(String[] args) { NameCollection nameCollection = new NameCollection(); nameCollection.addName("Alice"); nameCollection.addName("Bob"); nameCollection.addName("Charlie"); MyIterator<String> iterator = nameCollection.createIterator(); System.out.println("Iterating over names:"); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
Output
Iterating over names: Alice Bob Charlie
Advantages
- Encapsulation: The internal structure of the collection is hidden.
- Uniform Interface: Different types of collections can be traversed uniformly.
- Multiple Iterators: Multiple iterators can be created independently.
Limitations
- Adds additional classes.
- Not very useful for simple collections if you’re using Java’s built-in iterators.
When to Use
- When you need to iterate over different types of collections in a consistent way.
- When you want to decouple collection traversal logic from the collection itself.
- When implementing custom collections.
Java’s Built-in Example (For Comparison)
import java.util.*; public class JavaIteratorExample { public static void main(String[] args) { List<String> list = Arrays.asList("One", "Two", "Three"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }