Java Iterators and for-each Loop

3 minute read

Iteration is how you traverse collections. Java provides several mechanisms — from the simple enhanced for-loop to powerful Spliterator for parallel processing.


The Iterable and Iterator Interfaces

Iterable<T>                    Iterator<T>
├── iterator() → Iterator<T>   ├── hasNext() → boolean
                                ├── next() → T
                                └── remove()    (optional)

Any class implementing Iterable can be used in a for-each loop:

public interface Iterable<T> {
    Iterator<T> iterator();
}

public interface Iterator<T> {
    boolean hasNext();
    T next();
    default void remove() { throw new UnsupportedOperationException(); }
}

The Enhanced for-each Loop

The compiler transforms:

for (String name : names) {
    System.out.println(name);
}

Into:

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String name = it.next();
    System.out.println(name);
}

for-each works with any Iterable and arrays.


Using Iterator Directly

Use Iterator when you need to remove elements during iteration:

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie", "Bob"));

Iterator<String> it = names.iterator();
while (it.hasNext()) {
    String name = it.next();
    if (name.equals("Bob")) {
        it.remove();  // safe removal
    }
}
System.out.println(names);  // [Alice, Charlie]

ListIterator — Bidirectional

ListIterator extends Iterator with:

  • Backward traversal (hasPrevious(), previous())
  • Add during iteration (add())
  • Set the current element (set())
  • Index access (nextIndex(), previousIndex())
List<String> items = new ArrayList<>(List.of("A", "B", "C", "D"));
ListIterator<String> lit = items.listIterator();

// Forward
while (lit.hasNext()) {
    int index = lit.nextIndex();
    String item = lit.next();
    System.out.printf("[%d] %s%n", index, item);
}

// Backward
while (lit.hasPrevious()) {
    String item = lit.previous();
    System.out.println(item);  // D, C, B, A
}

Modifying During Iteration

ListIterator<String> lit = items.listIterator();
while (lit.hasNext()) {
    String item = lit.next();
    if (item.equals("B")) {
        lit.set("B2");       // replace current
        lit.add("B3");       // insert after current
    }
}
// [A, B2, B3, C, D]

Fail-Fast vs Fail-Safe

Fail-Fast Iterators

Most collection iterators (ArrayList, HashMap, HashSet) are fail-fast:

List<String> list = new ArrayList<>(List.of("A", "B", "C"));

for (String s : list) {
    if (s.equals("B")) {
        list.remove(s);  // ❌ ConcurrentModificationException
    }
}

They detect structural modification (add/remove) by another thread or the loop body, and throw immediately.

How it works: Each collection has a modCount field. The iterator snapshots it at creation. On each next(), it checks if modCount has changed.

Fail-Safe Iterators

Concurrent collections (CopyOnWriteArrayList, ConcurrentHashMap) use fail-safe iterators:

List<String> list = new CopyOnWriteArrayList<>(List.of("A", "B", "C"));

for (String s : list) {
    if (s.equals("B")) {
        list.remove(s);  // ✅ No exception — iterates over a snapshot
    }
}
Property Fail-Fast Fail-Safe
Throws ConcurrentModificationException ✅ ❌
Works on original collection ✅ ❌ (snapshot or copy)
Sees concurrent modifications Detects and fails Doesn’t see them
Examples ArrayList, HashMap CopyOnWriteArrayList, ConcurrentHashMap

Safe Removal Patterns

List<String> list = new ArrayList<>(List.of("A", "B", "C", "B", "D"));

// 1. Iterator.remove() — classic
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("B")) it.remove();
}

// 2. removeIf() — cleanest (Java 8+)
list.removeIf(s -> s.equals("B"));

// 3. Collect indices/items, remove after loop
List<String> toRemove = list.stream()
    .filter(s -> s.equals("B"))
    .toList();
list.removeAll(toRemove);

forEachRemaining() (Java 8+)

Process all remaining elements:

Iterator<String> it = names.iterator();
it.next();  // skip first
it.forEachRemaining(name -> System.out.println(name));

Spliterator — Parallel Iteration

Spliterator (Java 8) supports splitting a collection for parallel processing:

public interface Spliterator<T> {
    boolean tryAdvance(Consumer<? super T> action);
    Spliterator<T> trySplit();  // splits into two halves
    long estimateSize();
    int characteristics();
}
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8);
Spliterator<Integer> s1 = numbers.spliterator();
Spliterator<Integer> s2 = s1.trySplit();  // s2 gets first half

// s1: [5, 6, 7, 8]
// s2: [1, 2, 3, 4]

In practice, you rarely use Spliterator directly — parallelStream() uses it under the hood.

Spliterator Characteristics

Characteristic Meaning
ORDERED Elements have a defined order
DISTINCT No duplicates (Set)
SORTED Elements are sorted (TreeSet)
SIZED Size is known
NONNULL No null elements
IMMUTABLE Collection can’t be modified
CONCURRENT Safe for concurrent modification
SUBSIZED Split parts also have known size

Creating a Custom Iterable

public class NumberRange implements Iterable<Integer> {
    private final int start;
    private final int end;

    public NumberRange(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            private int current = start;

            @Override
            public boolean hasNext() {
                return current <= end;
            }

            @Override
            public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                return current++;
            }
        };
    }
}

// Usage
for (int n : new NumberRange(1, 5)) {
    System.out.print(n + " ");  // 1 2 3 4 5
}

Key Takeaways

  1. for-each is syntactic sugar for Iterator — works with any Iterable or array.
  2. Iterator.remove() is the only safe way to remove during iteration (or removeIf()).
  3. ListIterator adds bidirectional traversal plus add() and set().
  4. Fail-fast iterators detect concurrent modifications; fail-safe iterate over snapshots.
  5. Spliterator enables parallel iteration — used by parallelStream().

What’s Next?

Next: Collections Utility Class — sorting, searching, unmodifiable wrappers, and utility methods.

Updated: