Java Collection Iterators

With this article we will kick-off a new series where we will look deep into Java’s different collection solutions which are available in the version 1.8. For concrete implementations (like ArrayList or TreeSet) we will give you some use-cases where these collections will excel and some cases where you should use different collections. And of course we will write about the fluent API introduced with Java 8.

In this article we will start with the Iterator interface which is the base of all collection-navigation in Java.

About Iterators

Iterator is the basic concept when you want to deal with iterating diverse collections. As its name already suggest, it is used to iterate over the elements of collections.

This interface was introduced in the version 1.2 of Java. Until that you could use the Enumeration interface – which is still present but it’s encouraged to use collections based on the Iterator interface.

If we look at the interface definition, it is very slim:

package java.util;

import java.util.function.Consumer;

public interface Iterator {

    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

The interface supports 4 operations:

  • hasNext: this method returns true, if the current iterable object has more elements to access.
  • next: this method returns the next object in the current iterable. If there are no more objects, you get java.util.NoSuchElementException.
  • remove: this method is used to remove elements from the given iterable. As you can see, it is a default method. This means, not every implementation of the Iterator interface has to implement this method. And if it is not implemented, you’ll get a java.lang.UnsupportedOperationException.
  • forEachRemaining: this method calls the provided action on each remaining element in this iterable – or until the action throws an exception.

As you can see, this interface has the right amount of methods to get you started with iteration. The only thing you cannot do is adding elements to the iterable. But we can live with it.

The only limitation is, as you are already thinking, that only forward iteration is possible.

Later in this series we will take a look at ListIterator which extends Iterator and is a specialized to handle list traversal and adds some useful methods.

Examples

Now it’s time for some examples using the Iterator interface. Because we didn’t introduce any other collections, let’s use lists – created with java.util.Arrays.asList.

Iterating

This is the very fundamental use-case: iterating:

 Iterator
                iterator = Arrays.asList("HEARTS", "SPADES", "CLUBS", "DIAMONDS").iterator();
        
while (iterator.hasNext()) {

            System.out.println(iterator.next());

        }

This results in the following, as you may expect:

HEARTS
SPADES
CLUBS
DIAMONDS

Even though iterating is easy, it has some pitfalls:

        Iterator suits = Arrays.asList("HEARTS", "SPADES", "CLUBS", "DIAMONDS").iterator();

        Iterator ranks = Arrays.asList("2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A").iterator();

        while (ranks.hasNext()) {
while (suits.hasNext()) {
System.out.println(suits.next() + " " + ranks.next());
}
}

This results in:

HEARTS 2
SPADES 3
CLUBS 4
DIAMONDS 5

This has a minor error, but if we switch the order how the iterators are executed, we get an error:

        Iterator suits = Arrays.asList("HEARTS", "SPADES", "CLUBS", "DIAMONDS").iterator();

        Iterator ranks = Arrays.asList("2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A").iterator();

        while (suits.hasNext()) {
while (ranks.hasNext()) {
System.out.println(suits.next() + " " + ranks.next());
}
}

results in

HEARTS 2
SPADES 3
CLUBS 4
DIAMONDS 5
Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)

And this is not something you’d like.

Using the iterator after the loop

A common problem developers encounter at the beginning is that they access iterators after the loop:

        Iterator iterator = Arrays.asList(1, 4, 223, 56, 23, 88, 346, 78, 45, 33).iterator();

        while (iterator.hasNext()) {
iterator.next();
}
iterator.next();

This results, depending on the implementation of the Iterator in different exceptions, but if the implementation honors the contract of the interface, you’ll get a java.util.NoSuchElementException:

Exception in thread "main" java.util.NoSuchElementException
    at java.util.AbstractList$Itr.next(AbstractList.java:364)

This means, you can navigate one iterator only once to the end.

Using for loops

The solution to the problems above is to use a for-loop. And besides solving the above problems, it makes your code cleaner and more readable. And it avoids using the iterator after the loop to cause exceptions:

        for (Iterator ranks = Arrays.asList("2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A").iterator(); ranks.hasNext(); ) {

            String rank = ranks.next();

            for (Iterator suits = Arrays.asList("HEARTS", "SPADES", "CLUBS", "DIAMONDS").iterator(); suits.hasNext(); ) {

                String suit = suits.next();
System.out.println(suit + " " + rank);
}
}

Alternatively, you can utilize the enhanced for-loop to get your solution more readable:

for (String rank : Arrays.asList("2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A")) {
    for (String suit : Arrays.asList("HEARTS", "SPADES", "CLUBS", "DIAMONDS")) {
        System.out.println(suit + " " + rank);
    }
}

And as a nice side-effect we didn’t even need the knowledge of how the Iterator interface works – it’s all capsuled behind the caring hands of the Java runtime.

Removing elements

Another common problem is while using collections, to remove elements. The most viable solution which doesn’t result in an error or replicating lists is to use the Iterator interface:

List numbers = new ArrayList<>(Arrays.asList(1, 4, 223, 56, 23, 88, 346, 78, 45, 33));
Iterator iterator = numbers.iterator();
while (iterator.hasNext()) {
    if (iterator.next() > 100) {
        iterator.remove();
    }
}

This code above removes all the elements from the list which are greater than 100. As you can see, here we needed to wrap the whole creation of the list into an explicit ArrayList constructor. This is because how java.util.Arrays works: it returns a special ArrayList which doesn’t support modification. Therefore, if you try to remove elements, you’ll get an exception.

Again, take care how you use iterators!

List numbers = new ArrayList<>(Arrays.asList(1, 4, 223, 56, 23, 88, 346, 78, 45, 33));
Iterator iterator = numbers.iterator();
while (iterator.hasNext()) {
    if (iterator.next() < 0 || iterator.next() > 100) {
        iterator.remove();
    }
}

System.out.println(numbers);

This code results in the following:

[1, 4, 223, 56, 23, 88, 346, 78, 45, 33]

As you can see, the elements are unchanged. Why? Because the if condition calls iterator.next() for both sides of the conditional and in this case we skip the big values.

If the list would contain an odd number of elements, we would get an exception instead of an unexpected result.

We will see in a later article, that there is a more better and readable way to remove elements from collections.

forEachRemaining

The forEachRemaining method is new with Java 8. It gets a java.util.function.Consumer as parameter which is called for every remaining element of the iterator.

Currently, I don’t see any use-cases which could benefit from this new method, but there should be if the developers of Java added it to the mix.

Anyhow, imagine, you want to print all the elements of the iterator which are after the first element which is greater than 100. You can do it like this:

There are some different solutions too, but they involve custom logic. But with this new method you can solve it like this:

Iterator iterator = Arrays.asList(1, 4, 223, 56, 23, 88, 346, 78, 45, 33).iterator();

while (iterator.hasNext()) {
    if (iterator.next() > 100) {
        break;
    }
}
iterator.forEachRemaining(System.out::println);

The result is:

56
23
88
346
78
45
33

And if the iterator is over without any elements greater your target, it doesn’t throw an exception either:

Iterator iterator = Arrays.asList(1, 4, 223, 56, 23, 88, 346, 78, 45, 33).iterator();

while (iterator.hasNext()) {
    if (iterator.next() > 500) {
        break;
    }
}
iterator.forEachRemaining(System.out::println);

Conclusion

The Iterator interface is the basic solution if you have a collection of elements and you want to navigate through them – and remove elements from this collection.

The approaches here are limited: you cannot add elements to this collection, you cannot navigate backwards… And with Java 1.5, the new Collection interface was introduced which made our developer life easier.

In the next article we will look at the java.util.Collection and java.util.Iterable interfaces to get the foundation of the later articles which deal with the different collection-implementations.

 

Leave A Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.