Iterators in Java — theory with a playlist, Iterator, Iterable and ListIterator (and the for-each that wouldn’t let me delete)

I was putting together a playlist for a road trip and wanted to remove the songs shorter than two and a half minutes. One for-each, an if with a remove inside, and on to the next thing. Java answered with this:

Hold on!: ConcurrentModificationException
Left: [Dawn (3:35), Highway (3:00), Tide (3:20)]

*”Hold on? But it deleted Lighthouse!”* And it’s true: the song vanished from the list and Java complained anyway. (My real program had no try/catch, so it simply stopped dead; here I catch the exception only so I can show you how the list ended up.) The explanation lies in something you use every day without seeing it: the for-each isn’t magic, it’s an iterator working behind the scenes.

Today we open the hood. You’ll see what Iterator and Iterable are and how they differ, why deleting while you traverse has to be done through the iterator, how to make your own class work in a for-each, and what ListIterator adds for going back and forth. If you’ve read the Comparable and Cloneable theory, you already know the trick: small interfaces with a clear contract. It’s also worth reviewing interfaces and exceptions, because three new exceptions show up here. And when you finish, the iterators practice is waiting with three complete programs.

Iterator: the cursor that walks through a collection

A playlist is a list of songs. To go through it, until now you did this:

for (Song s : list) {
    System.out.println(s);
}

But that for-each is just a convenient way of writing something else. Look at both programs together, the usual one and the one the compiler understands underneath:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TwoWays {
    public static void main(String[] args) {
        List<Song> list = new ArrayList<>();
        list.add(new Song("Dawn", 215));
        list.add(new Song("Highway", 180));
        list.add(new Song("Storm", 240));

        System.out.println("With for-each:");
        for (Song c : list) {
            System.out.println("  " + c);
        }

        System.out.println("With Iterator:");
        Iterator<Song> it = list.iterator();
        while (it.hasNext()) {
            Song c = it.next();
            System.out.println("  " + c);
        }
    }
}
With for-each:
  Dawn (3:35)
  Highway (3:00)
  Storm (4:00)
With Iterator:
  Dawn (3:35)
  Highway (3:00)
  Storm (4:00)

The output is exactly the same. What the for-each does is ask the list for an iterator (list.iterator()) and repeat “is there a next one? give me the next one”. That iterator is an object of type Iterator, an interface with three methods:

public interface Iterator<E> {
    boolean hasNext();   // are there elements left to visit?
    E next();            // moves forward and returns the next one
    void remove();       // optional: deletes the last one next() returned
}
MethodWhat it doesDetail
hasNext()Tells you whether there are elements leftIt does not advance: you can call it a thousand times in a row
next()Moves forward one position and returns that elementThrows NoSuchElementException if there are none left
remove()Deletes from the collection the last element next() returnedIt’s optional: some collections don’t allow it

Every time you call list.iterator() you get a new iterator, with its own cursor starting at the beginning. Two iterators over the same list don’t bother each other (as long as you don’t modify the list, as you’ll see).

hasNext() and next(): the beat of the traversal

Picture the iterator as a cursor that sits between two songs, not on top of one. At the start it’s before the first one. next() jumps over a song and hands it to you; hasNext() only checks whether there is another song ahead, without moving. Let’s see it with a two-song playlist:

import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

public class Exhausted {
    public static void main(String[] args) {
        List<Song> list = List.of(new Song("Lighthouse", 135), new Song("Tide", 200));
        Iterator<Song> it = list.iterator();

        System.out.println("Is there a next one? " + it.hasNext());
        System.out.println("next() -> " + it.next());
        System.out.println("Is there a next one? " + it.hasNext());
        System.out.println("next() -> " + it.next());
        System.out.println("Is there a next one? " + it.hasNext());
        try {
            it.next();
        } catch (NoSuchElementException e) {
            System.out.println("No more songs: " + e.getClass().getSimpleName());
        }
    }
}
Is there a next one? true
next() -> Lighthouse (2:15)
Is there a next one? true
next() -> Tide (3:20)
Is there a next one? false
No more songs: NoSuchElementException

Between the lines:

  • As long as there are songs left, hasNext() gives true. After the second call to next(), the cursor is at the end and it turns false.
  • If you call next() with the cursor already at the end, Java doesn’t return null or make something up: it throws NoSuchElementException, an unchecked exception (no try/catch required, but the program stops if nobody catches it).
  • That’s why the right pattern is always while (it.hasNext()) with a single call to next() inside the loop.

Calling next() without checking hasNext() first, or calling it twice in the same iteration of the loop. With an odd number of elements, the second call runs into the end and NoSuchElementException is thrown. If you need the element several times, keep it in a variable.

Iterable: what a for-each needs

If Iterator is “the traversal”, Iterable is “something that knows how to give you a traversal”. It’s another interface, and an even smaller one:

public interface Iterable<T> {
    Iterator<T> iterator();
}

A single method: give me an iterator. And here’s the key to the whole topic: the for-each only requires that whatever goes to the right of the colon is an array or an Iterable. When you write

for (Song s : list) { ... }

the compiler turns it into this:

Iterator<Song> it = list.iterator();
while (it.hasNext()) {
    Song s = it.next();
    ...
}
Iterable<T>Iterator<T>
What it isSomething that can be traversedThe traversal itself (the cursor)
Main methoditerator()hasNext(), next(), remove()
How many a list hasIt’s the list itselfA new one each time you ask
Works in a for-each?YesNo (you can’t write for (x : it))

All of Java’s collections (lists, sets…) are Iterable, and that’s why they all work in a for-each. (We’ll look at them calmly in the collections topic.) Arrays do too, though through a special route in the compiler: they don’t use an iterator but an index loop.

Iterable and Iterator look so alike that it’s easy to mix them up. A trick to remember: -able = “capable of being” (something that can be iterated); -or = “the one that does it” (the one that iterates).

Deleting while you traverse: the Iterator’s remove()

Back to the program from the beginning. This is what it did:

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;

public class Concurrent {
    public static void main(String[] args) {
        List<Song> list = new ArrayList<>(List.of(
                new Song("Dawn", 215), new Song("Lighthouse", 135),
                new Song("Highway", 180), new Song("Tide", 200)));

        try {
            for (Song c : list) {
                if (c.seconds() < 150) {
                    list.remove(c);    // we modify the list outside the iterator
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("Hold on!: " + e.getClass().getSimpleName());
        }
        System.out.println("Left: " + list);
    }
}

When the for-each starts, the list hands it an iterator. Inside, an ArrayList keeps count of how many times its structure has been modified (a counter called modCount), and the iterator notes that counter down when it starts. On every next() it checks that it hasn’t changed. I called list.remove(c) on my own, the counter changed, and the iterator, seeing that its count no longer matched the list’s, threw ConcurrentModificationException on the next next(). It’s not that the delete failed: Java simply prefers to stop rather than carry on with a traversal that can no longer be trusted.

The correct way to delete is to ask the iterator: it knows what it has returned and, when it deletes, it updates its own count so it doesn’t get upset.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveSongs {
    public static void main(String[] args) {
        List<Song> list = new ArrayList<>(List.of(
                new Song("Dawn", 215), new Song("Lighthouse", 135),
                new Song("Highway", 180), new Song("Tide", 200)));

        Iterator<Song> it = list.iterator();
        while (it.hasNext()) {
            Song c = it.next();
            if (c.seconds() < 150) {
                it.remove();            // removes the last song that next() returned
            }
        }
        System.out.println("Left: " + list);

        Iterator<Song> other = list.iterator();
        try {
            other.remove();              // we haven't called next() yet!
        } catch (IllegalStateException e) {
            System.out.println("remove() without next(): " + e.getClass().getSimpleName());
        }
    }
}
Left: [Dawn (3:35), Highway (3:00), Tide (3:20)]
remove() without next(): IllegalStateException

The rules of remove():

  • It deletes the element returned by **the last call to next()**. That’s why it goes right after it, normally inside an if.
  • It can only be called **once per next()**. Two remove() calls in a row, or one before the first next(), throw IllegalStateException (the second part of the program).
  • It’s an optional operation: immutable lists (like those from List.of(...)) throw UnsupportedOperationException if you try to delete. That’s why the program uses new ArrayList<>(List.of(...)), which can be modified.

Modifying the collection (with the list’s own add or remove) while you traverse it with a for-each or an iterator. The right thing is it.remove(). To add while you traverse, you’ll need a ListIterator, which you’ll see below.

If you only want to delete based on a condition, there is a shortcut that already does all this inside: list.removeIf(s -> s.seconds() < 150). You’ll see it working in the practice.

Creating your own Iterable

This is the part that takes the most effort and pays the most. If your class stores things, you can make it work in a for-each with just two ingredients: it must **implement Iterable**, and its iterator() method must return an object that knows how to traverse it. A hand-made playlist with an array:

import java.util.Iterator;
import java.util.NoSuchElementException;

public class Playlist implements Iterable<Song> {
    private final Song[] songs = new Song[10];
    private int total = 0;

    public void add(Song c) {
        songs[total++] = c;
    }

    @Override
    public Iterator<Song> iterator() {
        return new PlaylistIterator();
    }

    private class PlaylistIterator implements Iterator<Song> {
        private int position = 0;                  // the cursor of this traversal

        @Override
        public boolean hasNext() {
            return position < total;
        }

        @Override
        public Song next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return songs[position++];
        }
    }
}

Three pieces:

  1. implements Iterable<Song>: the label that tells the for-each “I can be traversed”.
  2. iterator() returns a new PlaylistIterator object: each traversal has its own cursor (position).
  3. PlaylistIterator is an inner class (declared inside Playlist, without static): that’s why it can see songs and total of the playlist that created it, without you passing them in.

Notice too that next() checks hasNext() and throws NoSuchElementException if no songs are left: that’s what the interface’s contract promises. And now, the nice part:

public class MyPlaylist {
    public static void main(String[] args) {
        Playlist list = new Playlist();
        list.add(new Song("Dawn", 215));
        list.add(new Song("Highway", 180));
        list.add(new Song("Tide", 200));

        for (Song c : list) {
            System.out.println(c);
        }
    }
}
Dawn (3:35)
Highway (3:00)
Tide (3:20)

A class made by me, in a for-each just like any Java list. I haven’t written any index loop outside the class: whoever uses Playlist doesn’t need to know there’s an array inside.

Storing the iterator in an attribute and always returning that same object from iterator(). The second for-each finds the cursor already at the end and traverses nothing. Every call to iterator() has to create a new iterator.

ListIterator: back and forth

Iterator only moves forward. Lists offer an improved version, ListIterator, which you get with list.listIterator() and which lets you go backwards, know which position the cursor is at, and modify the list while you traverse:

MethodWhat it does
hasPrevious() / previous()Like hasNext() / next(), but backwards
nextIndex() / previousIndex()Position of the element next() / previous() would return
set(x)Replaces the last element returned by next() or previous()
add(x)Inserts x right where the cursor is

And its behaviour is easier to understand with the cursor “between songs” that we talked about:

import java.util.List;
import java.util.ListIterator;

public class BackAndForth {
    public static void main(String[] args) {
        List<Song> list = List.of(
                new Song("Dawn", 215), new Song("Highway", 180), new Song("Lighthouse", 135));
        ListIterator<Song> it = list.listIterator();

        System.out.println("next()     -> " + it.next());
        System.out.println("next()     -> " + it.next());
        System.out.println("previous() -> " + it.previous());
        System.out.println("next()     -> " + it.next());
        System.out.println("nextIndex: " + it.nextIndex() + ", previousIndex: " + it.previousIndex());
    }
}
next()     -> Dawn (3:35)
next()     -> Highway (3:00)
previous() -> Highway (3:00)
next()     -> Highway (3:00)
nextIndex: 2, previousIndex: 1

After two next() calls, the cursor is between Highway and Lighthouse. previous() jumps backwards over Highway and returns it; the next next() returns it again, because the cursor was left right before it. That’s why nextIndex() is 2 and previousIndex() is 1 at the end. The most interesting uses (modifying and adding) are in the practice.

ListIterator exists only for lists. Other collections (sets, for example) have Iterator, because “backwards” makes no sense in them: there are no positions.

Typical mistakes at a glance

ConcurrentModificationException. You modified the collection outside the iterator while traversing it. Use it.remove().

NoSuchElementException. You called next() without checking hasNext(), or twice in the same iteration.

IllegalStateException when deleting. remove() before the first next(), or two remove() calls in a row without a next() in between.

Always returning the same iterator from iterator(). Each traversal needs its own.

Mixing up Iterable (the collection) with Iterator (the cursor). The for-each asks for an Iterable.

Watch it step by step in Python Tutor

An iterator is an object with a position variable, and that position is exactly what’s hard to picture. Python Tutor runs the code line by line and draws variables, objects and references. Choose Java, paste the program and press Visualize Execution. Here’s a “homemade” iterator with the same methods as Java’s, so you can watch its cursor move and check that two iterators over the same array don’t step on each other:

public class Tutor {
    static class Cursor {
        private final String[] data;
        private int position = 0;

        Cursor(String[] data) { this.data = data; }

        boolean hasNext() { return position < data.length; }

        String next() { return data[position++]; }
    }

    public static void main(String[] args) {
        String[] titles = {"Dawn", "Highway", "Lighthouse"};
        Cursor it = new Cursor(titles);
        Cursor other = new Cursor(titles);

        System.out.println("it   -> " + it.next());
        System.out.println("it   -> " + it.next());
        System.out.println("other -> " + other.next());
        while (it.hasNext()) {
            System.out.println("it   -> " + it.next());
        }
    }
}

Expected output:

it   -> Dawn
it   -> Highway
other -> Dawn
it   -> Lighthouse

What to watch, step by step:

  1. After creating it and other, you’ll see one array with three titles and two Cursor objects. Both point to the same array and have position equal to 0.
  2. With the first two calls to it.next(), the position of it goes from 0 to 1 and then to 2. Each next() returns the element that was there before the increment.
  3. In other.next() notice that only the position of other changes (it goes to 1). The cursor of it stays at 2: each iterator keeps its own position.
  4. In the while, it.hasNext() is true because 2 < 3. After the last next(), position is 3 and hasNext() turns false: that’s how the loop ends.

Summary and quick table

I want to…I writeDetail
Traverse without indexesfor (Song s : list)Works with anything that is Iterable
Traverse by handIterator<Song> it = list.iterator();With while (it.hasNext())
Move to the next elementSong s = it.next();One call per iteration
Delete while traversingit.remove();After next(), once per next()
Delete by a conditionlist.removeIf(condition)Shortcut, it already does it inside
Make my class work in a for-eachimplements Iterable<T>And iterator() returns a new iterator
Go back and forth in a listListIterator<T> it = list.listIterator();previous(), set(), add()

Three ideas to take with you:

  1. **The for-each is an Iterator in disguise:** it asks the Iterable for an iterator and repeats hasNext() / next().
  2. **To delete while you traverse, do it with it.remove()**, not with the list: otherwise, ConcurrentModificationException.
  3. **An Iterable makes new iterators:** every traversal gets its own cursor, and that’s why your own class can be used in a for-each.

And now, let’s practise

That completes the theory. In the iterators practice we delete songs without an exception, build a linked list of our own that works in a for-each, and walk a playlist in both directions with ListIterator. And if you want to review the previous topic first, go back to the Comparable and Cloneable theory.

Next step: practiceIterators in Java — practice with safe deletes, a linked list of your own and a two-way playlist (and the second for-each that printed nothing)

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

One Comment

Leave a Reply

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