Iterables and Iterators
The single contract — Iterable / Iterator — that unifies every container in the framework, and the for-each loop that rides on top of it
If you ask "what makes the Java Collections Framework feel coherent?"
the deepest honest answer is: every container is iterable, and they
are all iterable in the same way. Whether you have a List, a
Set, a Map's entrySet, or your own custom container, the way
you walk through it is the same. That uniformity is enforced by two
tiny interfaces: Iterable<E> and Iterator<E>.
The two interfaces
That is it. Two interfaces, four methods.
Iterable<E>says "I can produce a freshIterator<E>on demand."Iterator<E>says "I am a cursor — ask me if there are more elements, then ask me for the next one."
Every List, Set, Queue, and Deque in the JCF implements
Iterable. Map does not directly (it isn't a Collection), but
its keySet(), values(), and entrySet() views do.
The for-each loop is just sugar
Java's for-each loop (for (E x : iterable)) is compiled to a call
on iterator() and a while over hasNext / next. The two
fragments below produce identical bytecode:
If you write your own class and implement Iterable<E>, the for-each
loop just works on it. That is the return on implementing the
interface.
Writing an Iterable from scratch
The fastest way to internalize the contract is to write one. Here is
a tiny IntRange that produces the integers [start, end):
Three observations:
- The
Iteratoris created fresh by every call toiterator(). That is why you can iterate the sameIntRangetwice — each walk gets its own cursor. next()is required to throwNoSuchElementExceptionwhen called past the end. The contract is precise; library code relies on it.- The
IntRangeitself is immutable. The state of the walk lives in the iterator, not in the iterable. That separation is the whole trick.
Why a separate Iterator object?
A natural question: why doesn't the container itself carry the cursor? Two reasons, both fundamental.
1. Multiple walks at once. If the container held the cursor, you could not iterate it from two places at once.
A list with a single cursor inside it could never produce that nested-loop output.
2. Streaming sources. Some "iterables" are not collections at all
— they are generators. A live stream of events, a file's lines, a
network response — each can implement Iterable and produce a
cursor that pulls one element at a time. No need to materialize the
whole sequence in memory.
Iterator.remove(): safe in-place deletion
The big functional advance over the old Enumeration is
Iterator.remove(). It removes the element most recently returned
by next() from the underlying collection, in O(1) for most
implementations.
Why does this matter? Because mutating a list with list.remove(i)
inside a for-each loop is forbidden — it throws
ConcurrentModificationException.
ConcurrentModificationException: the fail-fast guarantee
JCF iterators are fail-fast: if the underlying collection is
modified by anyone other than the iterator itself during a walk,
the next call to next() throws.
This is a feature, not a bug. Detecting concurrent mutation early turns "mysterious data corruption" into "loud crash at the line of the mistake."
Iterating a Map
A Map is not a Collection, so it does not have its own
iterator(). Instead it exposes three views, each of which is
iterable:
Prefer entrySet() when you need both: it walks the map once. Using
keySet() and then m.get(k) walks it twice.
A multi-file example: an Iterable of an array
Let's wrap an int[] in an Iterable so it can be used in for-each.
This is exactly how Arrays.asList and friends work conceptually.
Practice
Build a class Countdown that, when iterated, yields integers from start down to 1 inclusive.
Countdowntakes one constructor argument:int start(assumestart >= 1).CountdownimplementsIterable<Integer>.- Iterating a
Countdown(3)should produce3,2,1in that order. - It must be safe to iterate the same
Countdowntwice — each walk should start over.
Expected output:
3
2
1
---
3
2
1
Test your understanding
Why is the cursor state stored in the Iterator, not in the Iterable?
Because iterators are smaller in memory
So multiple independent walks of the same container can happen at once, each with its own cursor
Because Iterator is older than Iterable
Because the JVM forbids state in interfaces
What exception do JCF iterators throw when the underlying collection is structurally modified during a walk (other than via the iterator itself)?
IllegalStateException
NoSuchElementException
ConcurrentModificationException
UnsupportedOperationException
Which is the preferred way to iterate both keys and values of a Map exactly once?
Iterate keySet() and call get(key) for each
Iterate values()
Iterate entrySet() and read getKey() / getValue() on each entry
Convert the map to a List and iterate that
The Generics Revolution
How Java 5 generics solved the type-safety problem the Collections Framework was missing, and what the world looked like before and after
Equality and Hashing
The equals / hashCode contract — what every Set, Map, and contains() check secretly depends on, and the bugs that lurk when you get it wrong