Iterators & the Iterable Interface
Iterators & the Iterable Interface
Every time you write a for-each loop over a List or Set, something is working quietly behind the scenes: the Iterator protocol. Understanding it lets you traverse collections safely, remove elements mid-loop without errors, and even build your own iterable data structures.
The Iterator Interface
The java.util.Iterator<E> interface defines three methods:
boolean hasNext()— returnstrueif there are more elements to visit.E next()— returns the next element and advances the cursor.void remove()— removes the element most recently returned bynext()from the underlying collection (optional operation).
You obtain an iterator from any collection through its iterator() method:
This is exactly what the for-each loop compiles down to. The compiler rewrites for (String c : cities) into the while (it.hasNext()) form above.
Removing Elements Safely During Iteration
A classic beginner mistake is calling list.remove() inside a for-each loop. This throws a ConcurrentModificationException because the collection detects that it was structurally changed while an iterator is active.
ConcurrentModificationException.
The correct approach is to use Iterator.remove() instead:
The rule is simple: always call next() before remove(). Calling remove() twice in a row without an intervening next() throws IllegalStateException.
collection.removeIf(predicate) is even cleaner for bulk removal and handles the iterator internally. Use it when you do not need per-element logic beyond a simple condition: cities.removeIf(c -> c.startsWith("D"));
The Iterable Interface
The java.lang.Iterable<T> interface has just one required method:
Any class that implements Iterable can be used in a for-each loop. All standard collection interfaces (Collection, List, Set, Queue) extend Iterable, which is why they all support for-each.
Implementing Iterable on a Custom Class
Suppose you have a simple range type that represents a sequence of integers from start to end (exclusive). By implementing Iterable<Integer> you get for-each support for free:
Usage is idiomatic Java:
iterator() must return a fresh, independent iterator. If you return the same object twice, the second for-each loop starts mid-sequence or is already exhausted. The anonymous inner class above captures current as a local variable, so every iterator() call creates a new instance with current = start.
ListIterator — Bidirectional Traversal
java.util.ListIterator<E> extends Iterator and adds backwards traversal and in-place replacement, available on any List:
You can also traverse backwards with hasPrevious() and previous(), or insert elements with add().
When to Use an Iterator Explicitly
In day-to-day code, prefer the for-each loop or stream pipelines. Reach for an explicit iterator only when you need to:
- Remove elements during traversal (use
Iterator.remove()orremoveIf()). - Replace elements in a
Listduring traversal (useListIterator.set()). - Interleave two iterators of the same collection in a single loop.
- Implement a custom
Iterabletype.
Summary
Iterator<E> provides a uniform traversal protocol: hasNext(), next(), and the safe remove(). The Iterable<T> interface — whose single method returns an Iterator — is what unlocks the for-each loop for any type. When you need to delete while iterating, always go through the iterator's own remove() method, not the collection's. And when you design a custom container, implementing Iterable is the clean way to grant clients idiomatic traversal.