Concepts / Iterators and For-Each Loops

Iterators and For-Each Loops

An iterator is an object that traverses a collection, and the same calling pattern works across different collection types.

  • Programming

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
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.

Annindex 0iteratorafter one next() callBenindex 1Cyindex 2just returned by next()next() returns this
Where does the iterator sit relative to the elements, and which element does next() return as it moves forward? Here the cursor has just moved past Ann, so the next call returns Ben.

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

Definition

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 by next(). 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.

java
Notes on the code
  • coll.iterator() is called exactly once, before the loop starts
  • iter.hasNext() is checked before every call to iter.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

coll.iterator()called oncehasNext()items remain?next()take one itemprocess itemoptional remove()loop endsnothing lefttruerepeatfalse
What is the order of checking hasNext(), calling next(), and calling remove(), and when does the loop stop? The check comes first on every pass, and the loop ends the first time hasNext() reports that nothing remains.

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.

Worked example

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.

  1. 1
    Get the iterator

    coll.iterator() is called once. The cursor sits before Ann.

  2. 2
    First pass

    hasNext() reports true. next() returns Ann and advances past it. Ann is printed.

  3. 3
    Second pass

    hasNext() reports true. next() returns Ben and advances past it. Ben is printed.

  4. 4
    Third pass

    hasNext() reports true. next() returns Cy and advances past it. Cy is printed.

  5. 5
    Final check

    hasNext() reports false because nothing remains. The loop ends without calling next() again.

Result

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.

Before
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.

After
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.

RedRednullremoved by iter.remove()BlueBlueGreennullremoved by iter.remove()Greenkeptkeptkept
After iter.remove() deletes the element just returned by next(), how do the collection contents change? The null entries are removed, and the remaining items keep their order.

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.

java
Notes on the code
  • 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 like
    while (true) { String item = iter.next(); } runs past the last item.
    Why it fails

    Once no items remain, next() has nothing to return, and the traversal throws NoSuchElementException.

    Do this instead

    Use while (iter.hasNext()) and call next() only inside the loop body.

  • Common mistake

    Expecting next() to peek at an item

    What it looks like
    String first = iter.next(); followed by a second call that expects to see the same item.
    Why it fails

    next() reads and advances. The interface has no peek operation, so the item is consumed by the call.

    Do this instead

    Store 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 like

    Calling iter.remove() at the top of the loop body, before next() has run.

    Why it fails

    remove() deletes the item most recently returned by next(), so it needs a prior next() call to refer to.

    Do this instead

    Call next() first, then remove() in the same pass.

  • Common mistake

    Assuming every collection supports removal

    What it looks like

    Calling iter.remove() on a collection that does not allow removal.

    Why it fails

    Removal depends on the collection supporting it, and an unsupported removal may throw UnsupportedOperationException.

    Do this instead

    Confirm 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

MEDIUM

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 call remove().
  • 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 that remove() deletes.

Key Takeaways

  1. 1
    An iterator is an object that traverses a collection, and each collection type supplies its own iterator while the calling pattern stays the same.
  2. 2
    next() returns the next item and advances, hasNext() reports whether items remain, and remove() deletes the item most recently returned by next().
  3. 3
    Checking hasNext() before every call to next() prevents NoSuchElementException.
  4. 4
    Removal through an iterator depends on the collection supporting it, and an unsupported removal may throw UnsupportedOperationException.
  5. 5
    The 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 by next().
  • Check hasNext() before every next() call to avoid NoSuchElementException.
  • Removal through an iterator depends on collection support, and an unsupported removal may throw UnsupportedOperationException.