Iterator
A ListIterator moves in both directions and can add, set, and remove items, and it uses the same cursor model for all of them.
Why One Loop Shape Can Serve Many Collections
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.
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. This article starts with the calling pattern, then explains the cursor model behind the more powerful ListIterator used for lists.
The Calling Pattern Behind Every 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 that matter for a basic traversal, and each plays a different role in the loop. The method hasNext() reports whether any items remain. The method next() returns the next item and advances the iterator. The method remove() deletes the item most recently returned by next().
Walking the Loop Step by Step
Read the loop from top to bottom as a conversation between the loop, the iterator, and the collection. The collection is asked for an iterator exactly once. After that, the loop alternates between asking whether items remain and asking for the next one. The loop never touches the collection's internal storage. Everything it needs passes through the iterator, which is why the same loop shape works for arrays, linked lists, and trees alike.
Printing a collection with the generic loop
A collection coll holds the Strings apple, mango, and plum in that order. Trace what the while loop prints.
- 1First check
hasNext()returns true because items remain. The iterator sits before apple. - 2First pass
next()returns apple and moves past it. The loop prints apple. - 3Second pass
hasNext()is true.next()returns mango, and the loop prints mango. - 4Third pass
next()returns plum, and the loop prints plum. - 5Final check
hasNext()returns false because nothing is left, so the loop ends without callingnext()again.
The loop prints apple, mango, and plum, one per line, in list order.
Where the ListIterator Cursor Sits
Lists keep their elements in a definite order, and many tasks need to move both forward and backward, or to insert and replace items at a chosen spot. For that reason Java provides a richer kind of iterator for lists. The method list.listIterator() returns a ListIterator<T>, an interface that extends Iterator<T>. Everything an ordinary iterator can do, a ListIterator can also do, and it adds more.
The most useful mental model is to stop thinking of an iterator as sitting on an item. Instead, think of it as pointing to a position. That position is either at the very beginning of the list, at the very end, or in the gap between two neighboring elements. Items are never under the iterator in this model; the iterator sits in the spaces between them. A list with n items always has n plus 1 possible cursor positions. For the three fruits apple, mango, and plum, there are four positions: before apple, between apple and mango, between mango and plum, and after plum. Keeping this count in mind makes insertion behavior predictable.
The cursor structure is practical because of how LinkedList<T> stores its items. Its nodes are doubly linked: each node holds a pointer to the next node and a pointer to the previous node. Because of those two pointers, the iterator can step forward and backward through the nodes without restarting from the front. LinkedList also keeps a tail pointer that marks the last node, which speeds up addLast() and getLast(), but that tail pointer is not needed for iterator movement itself.
How next, previous, add, set, and remove Act
Every method acts relative to the cursor's current gap. The two movement methods cross one item and hand that item back to you. The method next() moves one space to the right and returns the item it just passed. The method previous() moves one space to the left and also returns the item it passed. Both return values are what you can inspect while traversing.
The editing methods are where beginners most often get confused, because remove() and set() act on the item the cursor passed most recently, not on the gap itself. The method remove() deletes that most recently passed item, whether it was passed by next() or previous(). The method set(obj) replaces that same item instead of deleting it. The method add(obj) behaves differently: it inserts obj at the cursor's current gap, either between two existing items or at the front or end of the list. Remove and set need a recently passed item to act on, while add needs only the gap. That is why the sorted-insertion technique uses add() after the cursor has been positioned, and does not need next() immediately before it.
| Method | What it acts on | Effect on the list | Cursor after the call |
|---|---|---|---|
| next() | The gap, crossing one item to the right | No change to the items | Moves one gap to the right, after the item it returned |
| previous() | The gap, crossing one item to the left | No change to the items | Moves one gap to the left, before the item it returned |
| remove() | The item most recently passed | That item is deleted | Stays in the gap where the deleted item was |
| set(obj) | The item most recently passed | That item is replaced by obj | Unchanged, still beside the replaced item |
| add(obj) | The current gap | obj is inserted into that gap | Moves past the new item, so the next next() returns the item after it |
Effects of the ListIterator methods relative to the cursor
Tracing the Sorted-Insertion Routine
The sorted-insertion technique keeps a list in ascending order. The idea is to start the iterator at the front and walk forward past every item that is smaller than the new string. When the iterator reaches the first item that should come after the new string, it has found the right gap, and add() places the new string there. The only step that needs care is the comparison. The rule is simple: if newItem.compareTo(item) gives zero or a negative number, newItem belongs before item.
Follow the two directions the routine allows. If the new string is bigger than the current item, the loop keeps advancing. If the new string is less than or equal to the current item, the routine steps the iterator back one space with previous(), so the cursor sits in the gap just before that item, and then leaves the loop. The other exit happens when hasNext() is false, which means every item was smaller. The iterator is then already at the end, so add() appends the new string.
Inserting dog into a sorted list with an existing dog
The sorted list holds cat, dog, fox. Insert a new dog using the sorted-insertion routine and predict where it lands.
- 1Pass 1
next()returns cat and moves the cursor past it.dog.compareTo(cat)is positive, so the loop continues. - 2Pass 2
next()returns the existing dog and moves past it.dog.compareTo(dog)is zero. The rule treats zero as comes before, so the routine proceeds to step back. - 3Step back
previous()returns the existing dog and moves the cursor to the gap between cat and the existing dog. - 4Insert
The break leaves the loop, and
add(dog)places the new dog in that gap, in front of the existing dog.
The list becomes cat, dog, dog, fox. The new duplicate sits before the equal item, not after it.
What do you think happens?
In the dog example, if the comparison had used < 0 instead of <= 0, where would the new dog end up?
Reveal answer
After the existing dog, between dog and fox
With a strict less-than test, compareTo returning zero would not trigger the step back. The loop would pass the existing dog, and the new dog would be inserted after it, giving cat, dog, dog, fox in the same order, but the placement of the inserted item relative to equal items would differ.
Where Sorted Insertion Goes Wrong
The routine is short, so each mistake usually shows up as a subtle wrong position rather than a crash. The mistakes below are the ones most likely to produce an insertion that is almost right but out of order.
Common mistake Leaving out the previous() call before add()
What it looks likeThe sorted list is cat, dog, fox and the new item is dog. The loop stops at the existing dog without stepping back, then add() places the new dog after the existing dog.
Why it failsAfter
next()returns an item, the cursor sits after that item.add()then inserts on the far side of it, so the new item lands one position too late.Do this insteadWhen the comparison shows the insertion point, call previous() once so the cursor sits in the gap before the item, then call add().
Common mistake Using a comparison that treats equal items as come after
What it looks likeThe test is written as newItem.compareTo(item) < 0. With the new dog, the existing dog gives zero, so the loop passes it.
Why it failsThe rule in this technique treats zero as comes before. A strict test changes where equal items are placed, so duplicates end up in a different order than intended.
Do this insteadTest for zero or a negative result with compareTo(item) <= 0, so a new duplicate lands before equal items.
Common mistake Calling previous() when the comparison has not yet shown the insertion point
What it looks likeprevious() is called on every pass, even when the new item is bigger than the current item.
Why it failsThis moves the cursor backward each time, so the iterator keeps retracing items it has already passed and the insertion point is never reached in order.
Do this insteadCall previous() only inside the branch where newItem.compareTo(item) is zero or negative, then leave the loop.
Common mistake Expecting add() to use the last item returned by next()
What it looks likenext() returns owl and the programmer assumes add(cow) will go in front of owl.
Why it failsadd()acts on the current gap, not on the most recently returned item. Afternext(), the gap sits after owl, so the item goes after it.Do this insteadUse previous() to move the cursor back into the gap before the item, then add().
Removing Items While Walking
The same general loop can also remove items. Doing the removal through the iterator, with iter.remove(), keeps the traversal consistent. Changing the collection directly during the walk bypasses the iterator's bookkeeping about its position. The example below removes every null value from a collection of Color objects.
Iterator<Color> iter = coll.iterator();
while (iter.hasNext()) {
Color c = iter.next();
if (c == null) {
iter.remove();
}
}- 1The iterator is obtained once from the collection.
- 2
hasNext()decides whether to take another pass. - 3
next()returns the item for this pass. - 4If the item is null,
remove()deletes that exact item through the iterator. - 5The loop then tests
hasNext()again and continues with the remaining items.
hasNext()guards each call tonext(), so the loop never reads past the endnext()returns the current item and advances, so the removal acts on ititer.remove()deletes the item thatnext()just returned, and only when it is null
Predict what happens if you write the same removal loop without iter.remove(), and instead call coll.remove(c) directly from inside the loop. Which mistake from the previous section does this resemble?
Hints
- Think about what the iterator remembers about its position after you change the collection behind its back.
- The safe pattern in this article keeps every removal inside the iterator.
Practice Tracing an Insertion
The ascending list ant, bee, owl is traversed with the sorted-insertion routine to add cow. List every item the loop passes, state where previous() leaves the cursor, and give the final list.
Hints
- cow is bigger than ant and bee, so those two items are passed by
next(). - At owl,
cow.compareTo(owl)is negative, so the routine steps back withprevious().
Key Takeaways
- An iterator traverses a collection through one calling pattern,
hasNext(),next(), andremove(), so the same loop shape works across different collection types. - A ListIterator's cursor sits in one of n+1 gaps in a list of n items.
next()andprevious()cross an item,remove()andset()act on the item just passed, andadd()inserts into the current gap. - Sorted insertion walks forward past smaller items, steps back with
previous()whencompareToshows the insertion point, and then callsadd(). - Removal should go through
iter.remove()inside the loop, and only where the collection supports it.