The iterator pattern is one of approaches to access elements of a collection, alongside with streams. From a technical point of view, the iterator traverses elements in a sequential and predictable order. In Java the behavior of iterators is defined in java.util.Iterator contract, which is a member of Java Collections Framework.

Iterators are similar to enumerators, but there are differences between these concepts too. The enumerator provides indirect and iterative access to each element of a data structure exactly once. From the other side, iterators does the same task, but the traversal order is predictable. With this abstraction a caller can work with collection elements, without a direct access to them. Also, iterators allow to delete values from a collection during the iteration.

Do you want to increase your Java collections skills?

This topic is really huge, and a single post is not enough to cover all. That is why I wrote this Practical Guide. Everything you need in the one book. Do you want to become Java ninja?

Access elements of a collection

As it was mentioned before, the order of accessing elements is predictable, which means that the iterator traverses elements sequentially. Therefore, the general Java contract does not allow to access the particular element (however, that is possible using Commons Collections, even this violates the idea). In order to access the next element, use next() method. It returns an element or throws NoSuchElementException, when the iterator does not contain more elements.

In order to prevent this unchecked exception, you should call the hasNext() method prior to accessing an element.

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
    int x = iterator.next() * 2;
    System.out.println(x);
}

This code snippet demonstrates the usage of the iterator for sequential retrieving of elements and the printing of double value of each element.

Graph 1. Access elements with iterator.next()

The important thing to note here, is that once elements are consumed, the iterator can not be used. That means, that calling the iterator after traversing will lead to an error:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
    int x = iterator.next() * 2;
    System.out.println(x);
}
int value = iterator.next();

The execution of the above code snippet will lead to the following result:

Graph 2. Calling the iterator without next elements

I already mentioned Apache Commons Collections. This library contains a helper class IteratorUtils, which has a number of static utility methods to work with iterators. While some of them violate the core pattern, they can be useful. So, alongside with a sequential access, it possible to access a particular element by its index, as well there is a wrapper method to get the first element.

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Iterator<Integer> iterator = numbers.iterator();
int first = IteratorUtils.first(iterator);
Assertions.assertThat(first).isEqualTo(1);

Consume an element

Since Java 8 iterators permit also to specify a consumer function, which can be performed on each element. This is a shortcut of what we can do using a while block. Let consider the first example implemented with the forEachRemaining() method. Take a look on the code snippet below:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Iterator<Integer> iterator = numbers.iterator();
iterator.forEachRemaining(number -> System.out.printf("This is a consumer. Value: %d \n", number*2));

This program listing produces the following result:

Graph 3. Using forEachRemaining() method

It is possible to perform such behavior with IteratorUtils. It has two static methods:

  • forEach() = applies the function to each element of the provided iterator.
  • forEachButLast() = executes the defined function on each but the last element in the iterator

Both methods accept two arguments: an iterator instance and a closure function. The closure defines a block of code which is executed from inside some block, function or iteration. Technically, it is a same thing as a consumer functional interface. The example of using this helper method is below:

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Iterator<Integer> iterator = numbers.iterator();
IteratorUtils.forEach(iterator, number -> System.out.printf("This is a closure. Value: %d \n", number*2));

The output of this snippet is:

Graph 4. Using Apache Commons Collections IteratorUtils.forEach() method

Remove elements

Finally, we need to observe how to use an iterator to delete elements. The java.util.Iterator interface defines the remove() method. It deletes from the underlying collection the last element returned by the iterator. This method can be called only once per call of the next() function. Note, that this operation is optional.

List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
System.out.println("Initial list: ");
numbers.stream().forEach(System.out::println);
Iterator<Integer> iterator = numbers.iterator();
while(iterator.hasNext()) {
    iterator.next();
    iterator.remove();
}
System.out.println("Modified list: ");
numbers.stream().forEach(System.out::println);

This code snippet executes and prints elements of the array list before and after removing elements, like it is presented on the screenshot below:

Graph 5. Remove elements with an iterator

There are things to remember, when you use iterator.remove():

  • As this operation is optional, it can throw the UnsupportedOperationException, if a collection does not have an implementation of this method (that is why I used an array list and not a generic list)
  • You must call the next() method BEFORE calling the remove() method, otherwise it will lead to the IllegalStateException to be thrown

That is how you can utilize an iterator in order to access elements of a collection, to perform an action on each element or to remove elements. If you have questions regarding the topic of this post, contact me using these ways.