1. Introduction
An Iterator is one of many ways we can traverse a collection, and as every option, it has its pros and cons.
It was first introduced in Java 1.2 as a replacement of Enumerations and:
- introduced improved method names
- made it possible to remove elements from a collection we’re iterating over
- doesn’t guarantee iteration order
In this tutorial, we’re going to review the simple Iterator interface to learn how we can use its different methods.
We’ll also check the more robust ListIterator extension which adds some interesting functionality.
2. The Iterator Interface
To start, we need to obtain an Iterator from a Collection; this is done by calling the iterator() method.
For simplicity, we’ll obtain Iterator instance from a list:
List<String> items = ...
Iterator<String> iter = items.iterator();
The Iterator interface has three core methods:
2.1. hasNext()
The hasNext() method can be used for checking if there’s at least one element left to iterate over.
It’s designed to be used as a condition in while loops:
while (iter.hasNext()) {
// ...
}
2.2. next()
The next() method can be used for stepping over the next element and obtaining it:
String next = iter.next();
*It’s good practice to use hasNext() before attempting to call next().*
Iterators for Collections don’t guarantee iteration in any particular order unless particular implementation provides it.
2.3. remove()
Finally, if we want to remove the current element from the collection, we can use the remove:
iter.remove();
This is a safe way to remove elements while iterating over a collection without a risk of a ConcurrentModificationException.
2.4. Full Iterator Example
Now we can combine them all and have a look at how we use the three methods together for collection filtering:
while (iter.hasNext()) {
String next = iter.next();
System.out.println(next);
if( "TWO".equals(next)) {
iter.remove();
}
}
This is how we commonly use an Iterator, we check ahead of time if there is another element, we retrieve it and then we perform some action on it.
2.5. Iterating With Lambda Expressions
As we saw in the previous examples, it’s very verbose to use an Iterator when we just want to go over all the elements and do something with them.
Since Java 8, we have the forEachRemaining method that allows the use of lambdas to processing remaining elements:
iter.forEachRemaining(System.out::println);
3. The ListIterator Interface
ListIterator is an extension that adds new functionality for iterating over lists:
ListIterator<String> listIterator = items.listIterator(items.size());
Notice how we can provide a starting position which in this case is the end of the List.
3.1. hasPrevious() and previous()
ListIterator can be used for backward traversal so it provides equivalents of hasNext() and next():
while(listIterator.hasPrevious()) {
String previous = listIterator.previous();
}
3.2. nextIndex() and previousIndex()
Additionally, we can traverse over indices and not actual elements:
String nextWithIndex = items.get(listIterator.nextIndex());
String previousWithIndex = items.get(listIterator.previousIndex());
This could prove very useful in case we need to know the indexes of the objects we’re currently modifying, or if we want to keep a record of removed elements.
3.3. add()
The add method, which, as the name suggests, allows us to add an element before the item that would be returned by next() and after the one returned by previous():
listIterator.add("FOUR");
3.4. set()
The last method worth mentioning is set(), which lets us replace the element that was returned in the call to next() or previous():
String next = listIterator.next();
if( "ONE".equals(next)) {
listIterator.set("SWAPPED");
}
It’s important to note that this can only be executed if no prior calls to add() or remove() were made.
3.5. Full ListIterator Example
We can now combine them all to make a complete example:
ListIterator<String> listIterator = items.listIterator();
while(listIterator.hasNext()) {
String nextWithIndex = items.get(listIterator.nextIndex());
String next = listIterator.next();
if("REPLACE ME".equals(next)) {
listIterator.set("REPLACED");
}
}
listIterator.add("NEW");
while(listIterator.hasPrevious()) {
String previousWithIndex
= items.get(listIterator.previousIndex());
String previous = listIterator.previous();
System.out.println(previous);
}
In this example, we start by getting the ListIterator from the List, then we can obtain the next element either by index –which doesn’t increase the iterator’s internal current element – or by calling next.
Then we can replace a specific item with set and insert a new one with add.
After reaching the end of the iteration, we can go backward to modify additional elements or simply print them from bottom to top.
4. Conclusion
The Iterator interface allows us to modify a collection while traversing it, which is more difficult with a simple for/while statement. This, in turn, gives us a good pattern we can use in many methods that only requires collections processing while maintaining good cohesion and low coupling.
Finally, as always the full source code is available over at GitHub.