Generic Programming with Collection<T>
An iterator is an object that traverses a collection, and the same calling pattern works across different collection types.
Writing One Loop for Many Storage Forms
Suppose you want one method that prints every item in a collection, no matter how that collection is stored. This article starts with the code that solves that problem, then explains the mechanism behind it. Read the code below first. The variable coll is assumed to be a collection of Strings. Do not worry yet about why each line is needed. Notice only how the loop is structured: it asks the iterator whether anything is left, takes one item, and does something with it.
What do you think happens?
Once the loop has already printed the last item in the collection, what does iter.hasNext() return on the next check?
Reveal answer
false
hasNext() reports whether items remain. After the last item has been returned by next(), no items remain, so hasNext() returns false and the while loop stops.
Why One Method Cannot Walk Every Structure
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 can take these and many other forms, a method written for one form cannot simply be reused for another. A loop written for array indices does not describe how to visit the nodes of a tree.
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 difference between a linked list walk and a tree walk is hidden inside the iterator, and the loop you write never has to see it.
Tracing the Cursor Through a Collection
A helpful way to picture an iterator is as a cursor that sits between items. It starts before the first item. Each call to next() moves the cursor forward past one item and hands that item back to you. Because of this, next() both reads and advances. The interface offers no way to peek at an item without consuming the step. Every look at an item is also a step forward.
The diagram shows the cursor after one call to next(). The item red has been returned, and the cursor now sits before null. The next call to next() will return null and advance past it. The remove() method, when available, deletes the item most recently returned by next(). That is why the cursor position matters: remove() always refers to the item just behind the cursor, not the item ahead of it.
The Three Iterator Methods
hasNext() reports whether items remain in the traversal. It returns true while there is at least one item the iterator has not yet returned, and false once every item has been returned.
next() returns the next item in the traversal and advances the iterator past it. Calling next() when no items remain is an error, which is why the loop checks hasNext() first.
remove() deletes the item most recently returned by next() from the collection. Whether this is allowed depends on the collection supporting removal through its iterator.
The Iterator interface is parameterized, 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. The interface defines exactly three methods, and each one plays a different role in the loop. The first two form the traversal itself, and the third changes the collection during that traversal.
The Order of Calls in the Loop
The diagram below shows the order of calls on each pass through the loop. Read it from top to bottom. The collection is asked for an iterator exactly once, before the loop begins. After that, the loop alternates between asking whether items remain and asking for the next one. The removal step is optional, so it appears only on passes where you choose to delete an item.
Two details are easy to miss in the diagram. First, the hasNext() and next() pair repeats once for every item, which is why the loop runs as many times as there are items. Second, the loop never touches the collection's internal storage. Everything it needs passes through the iterator. That is the reason the same loop shape works for arrays, linked lists, and trees alike.
Removing Items While Walking
The same general form can be used for other kinds of processing. Suppose the goal is to remove 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. The following version is an original illustration of that pattern using the same loop shape.
hasNext()decides whether to keep walkingnext()returns the item and moves the cursor past itremove()deletes the item thatnext()just returned, so it must come afternext()
Removing nulls from a four-item collection
The collection holds, in order, red, null, blue, and null. Trace the loop above and state what the collection holds when the loop ends.
- 1Start
The cursor sits before red.
hasNext()returns true because four items remain. - 2First pass
next()returns red and advances. c is not null, soremove()is not called. The collection still holds red, null, blue, null. - 3Second pass
hasNext()returns true.next()returns null and advances past it. c is null, soremove()deletes the item just returned, which is the first null. - 4Third pass
hasNext()returns true because blue and the second null remain.next()returns blue. c is not null, so nothing is removed. - 5Fourth pass
hasNext()returns true.next()returns the second null. c is null, soremove()deletes it. - 6Final check
hasNext()returns false because no items remain, so the while loop stops.
The collection holds red and blue, in that order. Every null was removed, and the loop never called next() without first checking hasNext().
Mistakes Beginners Make
Common mistake Calling next() without checking hasNext() first
What it looks likeIterator<String> iter = coll.iterator(); String first = iter.next(); String second = iter.next(); // may fail if only one item existsWhy it failsnext()assumes an item remains. When none remain, the traversal throwsNoSuchElementException.Do this insteadPut every next() call inside a loop guarded by while (iter.hasNext()).
Common mistake Expecting next() to peek at an item without moving forward
What it looks likeString item = iter.next(); // the cursor has already moved past this itemWhy it failsThe interface has no peek operation. Every call to
next()consumes a step.Do this insteadStore the returned value in a variable if you need it again, and do not expect a second call to return it.
Common mistake Calling remove() before any item has been returned
Why it failsremove()deletes the item most recently returned bynext(), so it has nothing to act on untilnext()has run at least once.Do this insteadCall remove() only after a next() call in the same pass, as in the Color example.
Common mistake Assuming every collection supports iter.remove()
Why it failsRemoval depends on the collection supporting it, and an unsupported removal may throw
UnsupportedOperationException.Do this insteadCheck the collection's documentation, or handle the exception, before depending on removal.
Practice: Trace a Removal
A collection holds, in order, blue, null, null, green. Using the removal loop from this article, list the value returned by each call to next(), the item removed on each pass where removal happens, and the final contents of the collection.
Hints
- Write one line per pass, starting from the cursor before blue.
- Remember that
remove()deletes the item returned by the most recentnext()call, not the item ahead of the cursor. - The loop ends only when
hasNext()returns false.
Key Takeaways
- An iterator is an object that traverses a collection, and each collection type supplies its own iterator internally, so the same calling pattern works across collection types.
hasNext()reports whether items remain,next()returns the next item and advances the iterator, andremove()deletes the item most recently returned bynext().- Checking
hasNext()before every call tonext()keeps the traversal from throwingNoSuchElementException. - Removal through an iterator depends on the collection supporting it, and an unsupported removal may throw
UnsupportedOperationException.