Iterator
An Iterator is an object that walks through the elements of a collection one at a time. It gives every collection, whether a list, a set, a queue, or a map view, the same simple interface for traversal, without exposing how the collection stores its data. The enhanced for loop is built on iterators, and an iterator is also the safe way to remove elements while looping.
The Iterator Interface
java.util.Iterator<E> has three methods:
| Method | Purpose |
|---|---|
boolean hasNext() | Is there another element? |
E next() | Return the next element and advance; throws NoSuchElementException if none remain |
void remove() | Remove the element most recently returned by next() (optional operation) |
Every collection provides one through its iterator() method:
import java.util.Iterator;
import java.util.List;
List<String> langs = List.of("Java", "Bash", "C");
Iterator<String> it = langs.iterator();
while (it.hasNext()) {
String lang = it.next();
System.out.println(lang);
}
Output:
Java
Bash
C
The cursor model
An iterator has a cursor that sits between elements:
start: ^ Java Bash C
after next(): Java ^ Bash C returned "Java"
after next(): Java Bash ^ C returned "Bash"
after next(): Java Bash C ^ returned "C"; hasNext() is now false
next() moves the cursor past one element and returns it. Calling next() when hasNext() is false throws NoSuchElementException. Always guard next() with hasNext().
An iterator is single-use: once exhausted, it cannot be reset. Call iterator() again for a fresh one.
Iterator and the Enhanced for Loop
A for-each loop over any Iterable is shorthand for an iterator loop. The compiler translates
for (String lang : langs) {
System.out.println(lang);
}
into the equivalent of
for (Iterator<String> i = langs.iterator(); i.hasNext(); ) {
String lang = i.next();
System.out.println(lang);
}
The for-each loop works with any object whose class implements java.lang.Iterable, an interface with a single method, iterator(). All Collection types implement it, and your own classes can too, as the practical example shows. (Arrays also work with for-each, but the compiler uses an index loop for them.)
Use for-each when you only need to read each element. Use an explicit iterator when you need to remove elements during traversal, or to control the traversal manually.
Removing Elements Safely
Modifying a collection directly while a for-each loop is iterating over it usually throws ConcurrentModificationException:
List<String> tags = new ArrayList<>(List.of("java", "", "sql", " ", "docker"));
for (String t : tags) {
if (t.isBlank()) tags.remove(t); // ConcurrentModificationException
}
The collection tracks a modification count. Its iterator records that count when created and checks it on each next(); if something other than the iterator itself changed the collection, the iterator fails fast rather than continuing with an inconsistent view. This check is best-effort, so some modifications go undetected, but the code is wrong either way.
The iterator's own remove() is the safe way:
Iterator<String> it = tags.iterator();
while (it.hasNext()) {
if (it.next().isBlank()) {
it.remove(); // removes the element just returned
}
}
System.out.println(tags); // [java, sql, docker]
Rules for remove():
- It removes the element returned by the most recent
next()call. - It may be called only once per
next(). Calling it twice, or before the firstnext(), throwsIllegalStateException. - It is optional: iterators of unmodifiable collections (
List.of,Set.of) throwUnsupportedOperationException.
For the common case of removing by condition, Collection.removeIf does the same thing more concisely and is usually preferred:
tags.removeIf(String::isBlank);
ListIterator: Two-Way Iteration on Lists
Lists provide a richer iterator, ListIterator, from list.listIterator():
| Method | Purpose |
|---|---|
hasPrevious(), previous() | Move backward |
nextIndex(), previousIndex() | Indexes around the cursor |
set(e) | Replace the element last returned by next() or previous() |
add(e) | Insert an element at the cursor position |
remove() | Remove the element last returned |
List<String> names = new ArrayList<>(List.of("ada", "linus", "grace"));
ListIterator<String> li = names.listIterator();
while (li.hasNext()) {
String n = li.next();
li.set(n.substring(0, 1).toUpperCase() + n.substring(1)); // replace in place
}
System.out.println(names); // [Ada, Linus, Grace]
ListIterator<String> back = names.listIterator(names.size()); // start at the end
while (back.hasPrevious()) {
System.out.print(back.previous() + " "); // Grace Linus Ada
}
listIterator(index) starts the cursor at a given position, which is how you iterate backward from the end.
Iterating Over Maps
A Map is not Iterable itself, but its views are:
Map<String, Integer> stock = new HashMap<>(Map.of("pen", 0, "book", 4, "bag", 0));
Iterator<Map.Entry<String, Integer>> it = stock.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> e = it.next();
if (e.getValue() == 0) {
it.remove(); // removes the entry from the map itself
}
}
System.out.println(stock); // {book=4}
Removing through an iterator of keySet(), values(), or entrySet() removes the corresponding entry from the underlying map. stock.values().removeIf(q -> q == 0) is the concise equivalent.
Implementing Iterable in Your Own Classes
Making a class implement Iterable<T> lets callers loop over it with for-each, while the class keeps its internal structure private. You supply an iterator() method that returns an Iterator<T>.
Key responsibilities when writing an iterator:
hasNext()must not change state; it can be called any number of times.next()must throwNoSuchElementExceptionwhen no elements remain.- If removal is not supported, leave
remove()unimplemented: its default implementation throwsUnsupportedOperationException.
Iterator Versus Other Ways to Loop
| Approach | Removes safely | Needs an index | Works on any collection |
|---|---|---|---|
Index for loop | Possible, but error-prone | Yes | Lists and arrays only |
| for-each loop | No | No | Any Iterable |
Explicit Iterator | Yes, via it.remove() | No | Any Iterable |
ListIterator | Yes, plus set and add | Optional | Lists |
forEach(lambda) | No | No | Any Iterable |
removeIf(predicate) | Yes (it is the removal) | No | Any Collection |
| Streams | Do not modify the source | No | Any Collection |
Practical Example: An Iterable Date Range
A scheduling feature needs to loop over every date in a sprint, count working days, and drop holidays. A small Iterable class lets callers use an ordinary for-each loop over dates.
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.*;
public class DateRange implements Iterable<LocalDate> {
private final LocalDate start;
private final LocalDate endInclusive;
public DateRange(LocalDate start, LocalDate endInclusive) {
if (endInclusive.isBefore(start)) throw new IllegalArgumentException("end before start");
this.start = start;
this.endInclusive = endInclusive;
}
@Override
public Iterator<LocalDate> iterator() {
return new Iterator<>() {
private LocalDate next = start;
@Override
public boolean hasNext() {
return !next.isAfter(endInclusive);
}
@Override
public LocalDate next() {
if (!hasNext()) throw new NoSuchElementException();
LocalDate current = next;
next = next.plusDays(1);
return current;
}
};
}
public static void main(String[] args) {
DateRange sprint = new DateRange(LocalDate.of(2026, 11, 13), LocalDate.of(2026, 11, 19));
List<LocalDate> workdays = new ArrayList<>();
for (LocalDate d : sprint) {
DayOfWeek dow = d.getDayOfWeek();
if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
workdays.add(d);
}
}
System.out.println("Working days: " + workdays.size());
System.out.println("First: " + workdays.get(0).getDayOfWeek() + " " + workdays.get(0));
Set<LocalDate> holidays = Set.of(LocalDate.of(2026, 11, 17));
Iterator<LocalDate> it = workdays.iterator();
while (it.hasNext()) {
if (holidays.contains(it.next())) {
it.remove();
}
}
System.out.println("After holidays: " + workdays);
}
}
Output:
Working days: 5
First: FRIDAY 2026-11-13
After holidays: [2026-11-13, 2026-11-16, 2026-11-18, 2026-11-19]
DateRange never stores the dates; its iterator computes each one on demand from a single next field, so a range of ten years uses no more memory than a range of a week. Because the class implements Iterable<LocalDate>, the for-each loop in main works without the caller knowing anything about how dates are produced. The anonymous iterator follows the contract: hasNext() only checks state, and next() throws NoSuchElementException past the end. The second loop uses an explicit Iterator to remove the holiday from the list while traversing it, the one situation where a for-each loop cannot be used.