Iterators and For-Each Loops
An iterator is an object that traverses a collection, and the same calling pattern works across different collection types.
Why One Loop Does Not Fit Every Collection
Suppose you want a single method that prints every item in a collection, no matter how that collection is stored. The difficulty is that each familiar storage form has its own way of being walked. An array is walked with a for loop over its indices. A linked list is walked with a while loop that moves a pointer from node to node. A binary tree is walked with a recursive subroutine that performs an inorder traversal. Because collections take these and many other forms, a method written for one form cannot simply be reused for another.
What do you think happens?
Could the same loop code that walks an array by index also walk a linked list unchanged?
Reveal answer
No. An array is walked with a for loop over indices, while a linked list is walked by moving a pointer from node to node. The walking code differs from one storage form to the next.
This difference is the problem that iterators solve. Instead of writing a different walking loop for each storage form, the collection supplies an object that does the walking, and the calling code stays the same.
The solution this course uses is the iterator. An iterator is an object whose job is to traverse a collection. Each collection type supplies its own iterator internally, so the details differ from one type to another. What stays constant is how you call the iterator. Once you learn that calling pattern, the same loop shape works for any collection that provides an iterator.
The Iterator as a Cursor
A useful way to picture an iterator is as a cursor that sits between elements, not on top of them. Before the first call to next(), the cursor is before the first item. Each call to next() moves the cursor forward past one item and hands that item back. The next call to next() then returns the item that follows it.
The cursor model explains a point that surprises many learners. The interface offers no way to look at an item without consuming it. Calling next() both reads the item and advances past it, so every look at an item is also a step forward.
The Three Methods and Their Roles
An iterator is an object that traverses a collection, and the same calling pattern works across different collection types. The Iterator interface is parameterized and written Iterator<T>, where T is the type of the items. If a collection implements Collection<T> for some type T, then calling coll.iterator() returns an Iterator<T> with that same T.
hasNext()reports whether items remain in the traversal.next()returns the next item and advances the iterator. It both reads and moves forward.remove()deletes the item most recently returned bynext(). Whether this works depends on the collection supporting removal.
The interface defines exactly three methods, and each one plays a different role in the loop. hasNext() answers the question of whether to keep going, next() produces the next item, and remove() is an optional step that acts on the item that next() just produced.
Writing the Generic Loop
The loop shape is the same for every collection that supplies an iterator. Ask the iterator whether anything is left, take one item, and do something with it. The example below assumes coll is a collection of Strings. Notice only how the loop is structured.
coll.iterator()is called exactly once, before the loop startsiter.hasNext()is checked before every call toiter.next()iter.next()returns one item and moves the iterator forward
Testing hasNext() before each call to next() keeps the traversal from throwing NoSuchElementException. Removing the hasNext() check and calling next() on an exhausted iterator is the usual way this exception appears.
Tracing the Loop Step by Step
The diagram is read from top to bottom. The collection is asked for an iterator exactly once. After that the hasNext() and next() pair repeats, which is why those messages occur many times. The loop never touches the collection's internal storage. Everything it needs passes through the iterator, which is the reason the same loop shape works for arrays, linked lists, and trees alike.
Walking a Three-Item Collection
A collection holds the Strings Ann, Ben, and Cy in that order. Trace the calls made by the generic loop that prints each item.
- 1Get the iterator
coll.iterator()is called once. The cursor sits before Ann. - 2First pass
hasNext()reports true.next()returns Ann and advances past it. Ann is printed. - 3Second pass
hasNext()reports true.next()returns Ben and advances past it. Ben is printed. - 4Third pass
hasNext()reports true.next()returns Cy and advances past it. Cy is printed. - 5Final check
hasNext()reports false because nothing remains. The loop ends without callingnext()again.
The output is Ann, Ben, Cy. The loop called hasNext() four times and next() three times.
Removing Items While You Walk
The same general form can be used for other kinds of processing. The next code removes every null value from a collection of Color objects. The removal is performed through the iterator, using iter.remove(), rather than by changing the collection directly during the walk. The source notes that this works as long as the collection supports removal.
Iterator<Color> iter = coll.iterator();
while (iter.hasNext()) {
Color c = iter.next();
// c is null: nothing removed yet
}The collection still contains the null entries, and the loop only reads them.
Iterator<Color> iter = coll.iterator();
while (iter.hasNext()) {
Color c = iter.next();
if (c == null) {
iter.remove();
}
}Each null returned by next() is deleted through the iterator as the walk passes it.
The For-Each Shorthand
Java also offers a for-each form that reads more compactly. It walks the same items in the same order, but the iterator is not named in the code, so you cannot call iter.remove() from inside it. When a loop needs removal, the explicit while loop with hasNext(), next(), and remove() is the form to use.
- The header String item : coll reads as each item in coll
- No iterator variable is visible, so
iter.remove()is not available here
Mistakes Beginners Make
Common mistake Calling next() without checking hasNext() first
What it looks likewhile (true) { String item = iter.next(); } runs past the last item.Why it failsOnce no items remain,
next()has nothing to return, and the traversal throwsNoSuchElementException.Do this insteadUse while (iter.hasNext()) and call next() only inside the loop body.
Common mistake Expecting next() to peek at an item
What it looks likeString first = iter.next(); followed by a second call that expects to see the same item.Why it failsnext()reads and advances. The interface has no peek operation, so the item is consumed by the call.Do this insteadStore the value in a variable on the call that reads it, and use that variable for the rest of the loop body.
Common mistake Calling remove() before any call to next()
What it looks likeCalling iter.remove() at the top of the loop body, before next() has run.
Why it failsremove()deletes the item most recently returned bynext(), so it needs a priornext()call to refer to.Do this insteadCall next() first, then remove() in the same pass.
Common mistake Assuming every collection supports removal
What it looks likeCalling iter.remove() on a collection that does not allow removal.
Why it failsRemoval depends on the collection supporting it, and an unsupported removal may throw
UnsupportedOperationException.Do this insteadConfirm the collection supports removal before relying on iter.remove().
Common mistake Changing the collection directly during the walk instead of using iter.remove()
Practice: Trace a Removal
A collection holds the Strings Ann, Ben, Cy, and Dee in that order. Write a while loop that uses iter.hasNext(), iter.next(), and iter.remove() to delete every item whose value is Ben. Then trace the sequence of calls and state which item is removed and what the iterator's next call would return afterward.
Hints
- Call
next()first on each pass, then decide whether to callremove(). - After removing Ben, the cursor is still positioned after Ben, so the next call returns Cy.
- Compare your trace against the cursor diagram: the item just returned by
next()is the one thatremove()deletes.
Key Takeaways
- 1An iterator is an object that traverses a collection, and each collection type supplies its own iterator while the calling pattern stays the same.
- 2
next()returns the next item and advances,hasNext()reports whether items remain, andremove()deletes the item most recently returned bynext(). - 3Checking
hasNext()before every call tonext()preventsNoSuchElementException. - 4Removal through an iterator depends on the collection supporting it, and an unsupported removal may throw
UnsupportedOperationException. - 5The for-each form is a compact shorthand, but it does not expose the iterator, so use an explicit while loop when items must be removed during the walk.
At a glance
- An iterator is an object that traverses a collection, and the same calling pattern works across different collection types.
next()returns the next item and advances;hasNext()reports whether items remain;remove()deletes the item most recently returned bynext().- Check
hasNext()before everynext()call to avoidNoSuchElementException. - Removal through an iterator depends on collection support, and an unsupported removal may throw
UnsupportedOperationException.