Iterators in Java

An Iterator walks a collection one element at a time, and it is the only safe way to remove during a traversal.

Definition

An Iterator is an object that produces the elements of a collection one at a time. Every class implementing Iterable can supply one, which is what makes the enhanced for loop work on any collection.

List<String> topics = new ArrayList<>(List.of("java", "sql", "css"));

Iterator<String> it = topics.iterator();
while (it.hasNext()) {
    String topic = it.next();
    System.out.println(topic);
}

The three methods

MethodPurpose
hasNext()Is there another element
next()Return the next element and advance
remove()Remove the element just returned; optional

The enhanced for loop is an iterator

for (String topic : topics) {
    System.out.println(topic);
}

// The compiler produces roughly this
for (Iterator<String> it = topics.iterator(); it.hasNext(); ) {
    String topic = it.next();
    System.out.println(topic);
}

ConcurrentModificationException

List<String> topics = new ArrayList<>(List.of("java", "sql", "css"));

for (String topic : topics) {
    if (topic.equals("sql")) {
        topics.remove(topic);      // ConcurrentModificationException
    }
}

Collections keep a modification counter. The iterator records it when created and checks it on every next(). Changing the collection through the collection itself moves that counter, the check fails, and the iterator refuses to continue.

This is fail fast behaviour. It is a safety feature, not a bug: it turns undefined traversal into an immediate, obvious error. The name mentions concurrency, but the same exception occurs in a single thread.

The three correct ways to remove

// 1. Iterator.remove - the classic answer
Iterator<String> it = topics.iterator();
while (it.hasNext()) {
    if (it.next().equals("sql")) {
        it.remove();
    }
}

// 2. removeIf - the clearest for a simple condition
topics.removeIf(topic -> topic.equals("sql"));

// 3. Collect what should survive
List<String> kept = topics.stream()
        .filter(topic -> !topic.equals("sql"))
        .toList();

ListIterator

List<String> topics = new ArrayList<>(List.of("java", "sql", "css"));

ListIterator<String> it = topics.listIterator();
while (it.hasNext()) {
    int index = it.nextIndex();
    String topic = it.next();

    if (topic.equals("sql")) {
        it.set("mysql");             // replace
        it.add("postgres");          // insert after the current element
    }
    System.out.println(index + ": " + topic);
}

// Walk backwards from the end
while (it.hasPrevious()) {
    System.out.println(it.previous());
}

ListIterator is available on lists only. It adds bidirectional movement, the current index, and in place set and add.

Iterator compared with ListIterator

FeatureIteratorListIterator
Available onAny CollectionList only
DirectionForwardBoth
Index availableNoYes
Can add or replaceNoYes

Fail fast and fail safe

Fail fastFail safe
ExamplesArrayList, HashMap, HashSetCopyOnWriteArrayList, ConcurrentHashMap
On modificationThrows immediatelyContinues
Iterates overThe live collectionA snapshot or a weakly consistent view
Sees later changesNot applicablePossibly not
List<String> safe = new CopyOnWriteArrayList<>(List.of("a", "b"));

for (String value : safe) {
    safe.add("c");        // no exception, but the loop never sees "c"
}

Iterating a Map

Map<String, Integer> counts = new HashMap<>(Map.of("java", 15, "sql", 5));

Iterator<Map.Entry<String, Integer>> it = counts.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> entry = it.next();
    if (entry.getValue() < 10) {
        it.remove();               // removes the entry from the map
    }
}

counts.entrySet().removeIf(entry -> entry.getValue() < 10);   // shorter

Making your own class iterable

public class Playlist implements Iterable<String> {

    private final List<String> tracks = new ArrayList<>();

    public void add(String track) {
        tracks.add(track);
    }

    @Override
    public Iterator<String> iterator() {
        return Collections.unmodifiableList(tracks).iterator();
    }
}
Playlist playlist = new Playlist();
playlist.add("track one");

for (String track : playlist) {      // works because Playlist is Iterable
    System.out.println(track);
}

Returning an iterator over an unmodifiable view stops callers removing elements through it.

Common mistakes

  • Calling next() without checking hasNext(), which throws NoSuchElementException.
  • Calling next() twice in one pass and skipping elements.
  • Removing through the collection while an iterator is active.
  • Calling remove() before next(), or twice in a row.
  • Assuming a fail safe iterator sees concurrent additions.

Best practices

  • Use the enhanced for loop for plain reading.
  • Use removeIf for conditional removal; it is clearer than an explicit iterator.
  • Use an explicit Iterator when removal depends on state built up during the walk.
  • Do not hold an iterator beyond the loop that uses it.
  • Implement Iterable when a class naturally represents a sequence.

Practice

  1. Reproduce ConcurrentModificationException and then fix it three different ways.
  2. Use a ListIterator to replace every negative number with zero.
  3. Why does the modification counter approach catch the problem immediately rather than later?
  4. Remove every map entry whose value is below a threshold, using an iterator and then using removeIf.
  5. Make a small class implement Iterable and loop over it with an enhanced for.

Conclusion

An iterator is the mechanism behind every enhanced for loop, and the only safe way to remove during a traversal. Prefer removeIf when the condition is simple, and treat ConcurrentModificationException as a helpful warning rather than an obstacle.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.