Iterators in Java
An Iterator walks a collection one element at a time, and it is the only safe way to remove during a traversal.
- Definition
- The three methods
- The enhanced for loop is an iterator
- ConcurrentModificationException
- The three correct ways to remove
- ListIterator
- Iterator compared with ListIterator
- Fail fast and fail safe
- Iterating a Map
- Making your own class iterable
- Common mistakes
- Best practices
- Practice
- Conclusion
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
Definition
An Iterator is an object that produces the elements of a collection one at a time. Every class implementing Iterable can supply one, which is what makes the enhanced for loop work on any collection.
List<String> topics = new ArrayList<>(List.of("java", "sql", "css"));
Iterator<String> it = topics.iterator();
while (it.hasNext()) {
String topic = it.next();
System.out.println(topic);
}The three methods
| Method | Purpose |
|---|---|
hasNext() | Is there another element |
next() | Return the next element and advance |
remove() | Remove the element just returned; optional |
The enhanced for loop is an iterator
for (String topic : topics) {
System.out.println(topic);
}
// The compiler produces roughly this
for (Iterator<String> it = topics.iterator(); it.hasNext(); ) {
String topic = it.next();
System.out.println(topic);
}ConcurrentModificationException
List<String> topics = new ArrayList<>(List.of("java", "sql", "css"));
for (String topic : topics) {
if (topic.equals("sql")) {
topics.remove(topic); // ConcurrentModificationException
}
}Collections keep a modification counter. The iterator records it when created and checks it on every next(). Changing the collection through the collection itself moves that counter, the check fails, and the iterator refuses to continue.
This is fail fast behaviour. It is a safety feature, not a bug: it turns undefined traversal into an immediate, obvious error. The name mentions concurrency, but the same exception occurs in a single thread.
The three correct ways to remove
// 1. Iterator.remove - the classic answer
Iterator<String> it = topics.iterator();
while (it.hasNext()) {
if (it.next().equals("sql")) {
it.remove();
}
}
// 2. removeIf - the clearest for a simple condition
topics.removeIf(topic -> topic.equals("sql"));
// 3. Collect what should survive
List<String> kept = topics.stream()
.filter(topic -> !topic.equals("sql"))
.toList();ListIterator
List<String> topics = new ArrayList<>(List.of("java", "sql", "css"));
ListIterator<String> it = topics.listIterator();
while (it.hasNext()) {
int index = it.nextIndex();
String topic = it.next();
if (topic.equals("sql")) {
it.set("mysql"); // replace
it.add("postgres"); // insert after the current element
}
System.out.println(index + ": " + topic);
}
// Walk backwards from the end
while (it.hasPrevious()) {
System.out.println(it.previous());
}ListIterator is available on lists only. It adds bidirectional movement, the current index, and in place set and add.
Iterator compared with ListIterator
| Feature | Iterator | ListIterator |
|---|---|---|
| Available on | Any Collection | List only |
| Direction | Forward | Both |
| Index available | No | Yes |
| Can add or replace | No | Yes |
Fail fast and fail safe
| Fail fast | Fail safe | |
|---|---|---|
| Examples | ArrayList, HashMap, HashSet | CopyOnWriteArrayList, ConcurrentHashMap |
| On modification | Throws immediately | Continues |
| Iterates over | The live collection | A snapshot or a weakly consistent view |
| Sees later changes | Not applicable | Possibly not |
List<String> safe = new CopyOnWriteArrayList<>(List.of("a", "b"));
for (String value : safe) {
safe.add("c"); // no exception, but the loop never sees "c"
}Iterating a Map
Map<String, Integer> counts = new HashMap<>(Map.of("java", 15, "sql", 5));
Iterator<Map.Entry<String, Integer>> it = counts.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
if (entry.getValue() < 10) {
it.remove(); // removes the entry from the map
}
}
counts.entrySet().removeIf(entry -> entry.getValue() < 10); // shorterMaking your own class iterable
public class Playlist implements Iterable<String> {
private final List<String> tracks = new ArrayList<>();
public void add(String track) {
tracks.add(track);
}
@Override
public Iterator<String> iterator() {
return Collections.unmodifiableList(tracks).iterator();
}
}Playlist playlist = new Playlist();
playlist.add("track one");
for (String track : playlist) { // works because Playlist is Iterable
System.out.println(track);
}Returning an iterator over an unmodifiable view stops callers removing elements through it.
Common mistakes
- Calling
next()without checkinghasNext(), which throwsNoSuchElementException. - Calling
next()twice in one pass and skipping elements. - Removing through the collection while an iterator is active.
- Calling
remove()beforenext(), or twice in a row. - Assuming a fail safe iterator sees concurrent additions.
Best practices
- Use the enhanced
forloop for plain reading. - Use
removeIffor conditional removal; it is clearer than an explicit iterator. - Use an explicit
Iteratorwhen removal depends on state built up during the walk. - Do not hold an iterator beyond the loop that uses it.
- Implement
Iterablewhen a class naturally represents a sequence.
Practice
- Reproduce
ConcurrentModificationExceptionand then fix it three different ways. - Use a
ListIteratorto replace every negative number with zero. - Why does the modification counter approach catch the problem immediately rather than later?
- Remove every map entry whose value is below a threshold, using an iterator and then using
removeIf. - Make a small class implement
Iterableand loop over it with an enhancedfor.
Conclusion
An iterator is the mechanism behind every enhanced for loop, and the only safe way to remove during a traversal. Prefer removeIf when the condition is simple, and treat ConcurrentModificationException as a helpful warning rather than an obstacle.