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
Iteratorinterface. - Aggregate (Collection): Interface that defines a method to create an iterator.
- Concrete Aggregate: A class that implements the
Aggregateinterface 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());
}
}
}
