Iterator is a behavioral design pattern which allows you to traverse elements of a collection without exposing its internal representation. This tutorial discusses iterator design pattern.

What is Iterator Design Pattern?

The important point with Iterator pattern is that it provides iteration logic but it does not expose the underlying data structure used to store the elements. This is particularly useful when we want to have multiple iteration logic. For example, Some of the available data structures like list, array provide inherent way to iterate all elements of the collection. If the collection stores the data using one of these data structures, we can easily iterate through all elements of the collection. However, if the underlying data structure is tree, we can iterate using breadth-first or depth-first traversal. We can add many types of traversal algorithms to the collection, but that’s not the purpose of the collection. It’s main purpose is to store data efficiently.

The client can implement the traversal algorithm but client should not care about how the data i stored in the collection. The iterator pattern creates an object with traversal logic for the collection. It decouples iteration logic from the collection. The iterator also encapsulates all the traversal details including the current position of the iteration, number of elements in the collection, etc. This allows multiple iterators to iterate a collection at the same time.

This pattern is useful when you have a collection with a complex data structure. You want to create iteration logic that hides the complexity from the client code. This also helps decouple traversal logic from the collection object. It reduces code duplication for traversal if we had to traverse through collection multiple times. In standard Java library, we have Iterable interface with collections implementing it to facilitate traversal of their elements.

How to implement Iterator Pattern?

The iterator pattern consists of following main components:

  • Iterator: This is an interface which declares the operations needed for traversing a collection. The primary methods are next() and hasNext(). We will have different concrete iterators with their own logic for traversing the collection.
  • Collection: This is and interface having multiple elements and needs iteration logic. So, it provides one or more methods for getting iterators that can iterate over its elements. These getter methods should declare with return type of Iterator interface so that it can return a concrete iterator. The concrete collection will implement these getter methdos to get specific type of iterator. It will also have other logic related to collection.
  • Client: Tthe client works with both iterator and the collection using the interface. This decouples client from the specific concrete class of collection.
  1. In this case, we are designing a simple DVD store, so we have DVDs.
 1public class DVD {
 2    private String title;
 3    private String category;
 4    private boolean available;
 5
 6    public DVD(String title, String category) {
 7        this.title = title;
 8        this.category = category;
 9        this.available = true;
10    }
11
12    public String getTitle() {
13        return title;
14    }
15
16    public String getCategory() {
17        return category;
18    }
19
20    public boolean isAvailable() {
21        return available;
22    }
23
24    public void setAvailable(boolean available) {
25        this.available = available;
26    }
27
28    @Override
29    public String toString() {
30        return "DVD{" +
31                "title='" + title + '\'' +
32                ", category='" + category + '\'' +
33                ", available=" + available +
34                '}';
35    }
36}

We also have DvdStore collection interface which declares the methods for the collection.

1public interface DvdStore {
2    void add(DVD dvd);
3    void remove(DVD dvd);
4    void addAll(List<DVD> dvds);
5    DvdIterator getIterator();
6}

and we have concrete implementation for it.

 1public class DvdStoreImpl implements DvdStore {
 2    private List<DVD> movies;
 3
 4    public DvdStoreImpl() {
 5        this.movies = new ArrayList<>();
 6    }
 7
 8    public void add(DVD dvd) {
 9        movies.add(dvd);
10    }
11
12    public void remove(DVD dvd) {
13        movies.remove(dvd);
14    }
15
16    public void addAll(List<DVD> dvds) {
17        movies.addAll(dvds);
18    }
19
20    public DvdIterator getIterator() {
21        return new SimpleDvdIterator(movies);
22    }
23}

In above code, note that getIterator() returns an interface defined below and not the concrete implementation of the iterator.

  1. In order to actually create an iterator object, first declare and interface for Iterator with required methods declared in it.
1public interface DvdIterator {
2    boolean hasNext();
3    DVD next();
4}
  1. Create concrete DvdIterator.
 1public class SimpleDvdIterator implements DvdIterator {
 2    private List<DVD> movies;
 3    private int currentPosition;
 4
 5    public SimpleDvdIterator(List<DVD> movies) {
 6        this.movies = movies;
 7        this.currentPosition = 0;
 8    }
 9
10    @Override
11    public boolean hasNext() {
12        return currentPosition < movies.size();
13    }
14
15    @Override
16    public DVD next() {
17        if (hasNext()) {
18            return movies.get(currentPosition++);
19        }
20        return null;
21    }
22}

Now, the client can iterate through all elements using simple interface without knowing the internal details of how DVDs are stored in DvdStore.

 1public class ClientMain {
 2    public static void main(String[] args) {
 3        DVD dvd1 = new DVD("Titanic", "Romance");
 4        DVD dvd2 = new DVD("Avatar", "Fantasy");
 5        DVD dvd3 = new DVD("True Lies", "Action");
 6
 7        List<DVD> dvds = List.of(dvd1, dvd2, dvd3);
 8        DvdStoreImpl dvdStore = new DvdStoreImpl();
 9        dvdStore.addAll(dvds);
10        DvdIterator dvdIterator = dvdStore.getIterator();
11
12        while (dvdIterator.hasNext()) {
13            System.out.println(dvdIterator.next().getTitle());
14        }
15    }
16}

Advantages:

  • This pattern decouples specific traversal algorithms from the specific collection they operate on.
  • Iterator pattern hides the internal representation of data in the collection from client. The client doesn’t need to know how the data is stored in the collection.
  • Iterator pattern promotes single responsibility principle. It also promotes Open/Closed principle. You can easily add new kinds of iteration logic in new iterators and use them without breaking anything.
  • This pattern also allows multiple code to iterate through same collection because each iterator object contains its own iteration state.

Disadvantages:

  • If the code contains simple data collection, implementing iterator pattern might be an overkill.
  • Iterator pattern do not provide access to current element index which might be important in some cases.

Summary:

  • Iterator pattern is an efficient way to implement complex traversal logic.
  • It hides the traversal algorithm from the client and allows for reuse of traversal code.
  • This pattern is based on an iterator interface and is widely used in Java standard language.