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 behaviour 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 inderect 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.
Access elements of a collection
As it was mentioned before, the order of accessing elements is predictable, which means that the iterator traverses elements sequentialy. 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.
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 exception:
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:
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);
Consuming 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:
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 second concept 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:
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 snown on the screenshot below:
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 from a collection. These are general considerations. If you want to learn how to use iterators with concrete types of collections (lists, sets, queues) and to learn more advanced iterator patterns - check my book on Java Collections Framework.
Top comments (2)
Cool article, Thanks
Thanks for your comment. You can find more articles on mednikov.tech