4.4. Iterator

Maheshmaddi
4 min readApr 10, 2023

--

The Iterator pattern is a behavioral design pattern that provides a way to access the elements of an aggregate object (e.g., a collection) sequentially without exposing its underlying representation. It promotes loose coupling between the collection and the code that traverses it.

The Iterator pattern is typically used when:

  1. You want to provide a uniform way to traverse different types of aggregate objects (collections).
  2. You want to encapsulate the traversal logic in a separate object, making the collection and traversal code more maintainable and flexible.
  3. You want to hide the internal structure of the collection while providing a standard way to access its elements.

To implement the Iterator pattern, follow these steps:

  1. Define an iterator interface or abstract class that specifies the common methods for traversing the collection (e.g., hasNext, next, and remove).
  2. Create a concrete iterator class that implements the iterator interface or extends the abstract class, providing the specific traversal logic for the collection.
  3. Implement the iterator methods in the concrete iterator class, maintaining the current position and state of the traversal.
  4. In the collection class, add a method to create and return an instance of the concrete iterator.
  5. In the client code, use the iterator object to traverse the collection without accessing its internal structure directly.

Here’s a simple example of the Iterator pattern in Java:

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

// Concrete iterator
class ConcreteIterator implements Iterator<String> {
private final String[] items;
private int position;

public ConcreteIterator(String[] items) {
this.items = items;
this.position = 0;
}

@Override
public boolean hasNext() {
return position < items.length;
}

@Override
public String next() {
return hasNext() ? items[position++] : null;
}
}

// Aggregate object (collection)
class ConcreteCollection {
private final String[] items;

public ConcreteCollection(String[] items) {
this.items = items;
}

public Iterator<String> iterator() {
return new ConcreteIterator(items);
}
}

// Client code
public class Client {
public static void main(String[] args) {
String[] items = {"A", "B", "C"};
ConcreteCollection collection = new ConcreteCollection(items);
Iterator<String> iterator = collection.iterator();

while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

In this example, the Iterator interface defines the common methods for traversing a collection. The ConcreteIterator class implements the Iterator interface and provides the specific traversal logic for the collection. The ConcreteCollection class represents the aggregate object and provides a method to create an iterator.

Advantages of the Iterator pattern:

  1. Decoupling: The Iterator pattern decouples the collection and traversal code, promoting maintainability and flexibility.
  2. Uniform traversal: The pattern provides a uniform way to traverse different types of collections without exposing their internal structure.
  3. Extensibility: New traversal algorithms can be added easily by creating new iterator classes without modifying the collection code.

Disadvantages of the Iterator pattern:

  1. Increased complexity: The Iterator pattern introduces additional classes and objects, increasing the overall complexity of the code.

When using the Iterator pattern, consider its benefits and drawbacks carefully. Use the pattern when you want to provide a uniform way to traverse collections, encapsulate the traversal logic, and hide the internal structure of the collection. Be aware of the potential complexity introduced by the pattern and ensure that it is applied judiciously to maintain a clean and understandable codebase.

Use Case: Iterator Pattern for Custom List Implementation

The Iterator pattern provides a way to traverse through a collection without exposing the underlying structure. Here’s a use case with Java code, implementing a simple custom list with an iterator:

  1. Define the Iterator interface:
public interface Iterator<T> {
boolean hasNext();
T next();
}

2. Define the Iterable collection interface:

public interface IterableCollection<T> {
Iterator<T> iterator();
}

3. Implement a custom list (a simple array-based list):

public class CustomList<T> implements IterableCollection<T> {
private Object[] items;
private int size;

public CustomList(int capacity) {
items = new Object[capacity];
size = 0;
}

public void add(T item) {
if (size == items.length) {
throw new IllegalStateException("List is full");
}
items[size++] = item;
}

@Override
public Iterator<T> iterator() {
return new CustomListIterator();
}

private class CustomListIterator implements Iterator<T> {
private int currentIndex;

public CustomListIterator() {
currentIndex = 0;
}

@Override
public boolean hasNext() {
return currentIndex < size;
}

@Override
@SuppressWarnings("unchecked")
public T next() {
if (!hasNext()) {
throw new NoSuchElementException("No more elements");
}
return (T) items[currentIndex++];
}
}
}

4. Use the custom list and iterator:

public class IteratorPatternDemo {
public static void main(String[] args) {
CustomList<String> list = new CustomList<>(5);
list.add("Apple");
list.add("Banana");
list.add("Cherry");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}

This example demonstrates a simple custom list implementation with an Iterator pattern. The iterator allows the client code to traverse the list without being aware of the underlying array structure.

Note: For complete list of design patterns click here

--

--