Java Iterators and for-each Loop
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
Iterableand 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
- for-each is syntactic sugar for
Iterator— works with anyIterableor array. Iterator.remove()is the only safe way to remove during iteration (orremoveIf()).ListIteratoradds bidirectional traversal plusadd()andset().- Fail-fast iterators detect concurrent modifications; fail-safe iterate over snapshots.
Spliteratorenables parallel iteration — used byparallelStream().
What’s Next?
Next: Collections Utility Class — sorting, searching, unmodifiable wrappers, and utility methods.