Skip to Content
Course content

47: Iterators and the Iterable Interface

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably reached a point in your Java journey where you're using the "enhanced for-loop" (the for-each loop) for almost everything. It's clean, it's concise, and it gets the job done. But there's a hidden trap in that syntax that almost every developer hits at least once. I've seen it in countless code reviews, usually coming from someone trying to clean up a list based on some condition.

The "Modify-While-Looping" Crash

List<String> activeUsers = new ArrayList<>(Arrays.asList("Alice", "Bob", "Charlie", "Dave"));

// Goal: Remove users whose names start with 'C'
for (String user : activeUsers) {
    if (user.startsWith("C")) {
        activeUsers.remove(user); 
    }
}
// Result: java.util.ConcurrentModificationException

At first glance, this looks perfectly logical. You're iterating through the list, you find a match, and you remove it. But the moment the code hits activeUsers.remove(user), Java throws a ConcurrentModificationException.

Here is what's actually happening under the hood: the for-each loop is actually "syntactic sugar." The compiler transforms that loop into an Iterator. The Iterator keeps track of the list's state. When you call activeUsers.remove(), you are modifying the list directly, but the Iterator doesn't know that happened. The next time the loop tries to call next(), the iterator notices the list has changed behind its back and panics, throwing the exception to prevent unpredictable behavior.

Solving the Conflict with Explicit Iterators

To fix this, we have to stop using the shortcut and use the Iterator explicitly. The Iterator object has its own remove() method. When you use iterator.remove(), the iterator updates the underlying collection and updates its own internal state simultaneously, keeping everything in sync.

Iterator<String> it = activeUsers.iterator();
while (it.hasNext()) {
    String user = it.next();
    if (user.startsWith("C")) {
        it.remove(); // This is the safe way to do it
    }
}

I generally recommend this approach whenever you need to mutate a collection while traversing it. It's explicit and it avoids the crash. If you're using Java 8 or later, you could also use removeIf(), which handles this iterator logic for you internally, but understanding the Iterator is fundamental to understanding how Java collections actually work.

The Contract of the Iterable Interface

You might be wondering: "How does the for-each loop even know how to work with an ArrayList, a HashSet, or a LinkedList?" The answer is the Iterable<T> interface.

Iterable is a very simple interface. Its primary job is to say: "I can provide an Iterator for you." If a class implements Iterable, it's promising that it has a method called iterator() that returns an Iterator object. This is why you can use the for-each loop on any class that implements Iterable.

The Iterator itself is the "worker" that knows how to navigate the specific data structure. For an ArrayList, the iterator just increments an index. For a LinkedList, the iterator follows the pointers from one node to the next. As a developer, you don't have to care about those details; you just call hasNext() to see if there's more and next() to grab the item.




📋 Practical Task

Implementing a Custom MusicPlaylist Collection

Imagine you are building a music app. You have a MusicPlaylist class that holds a private array of song titles. Currently, the only way to see the songs is to use a getter for the whole array, which breaks encapsulation.

Your task is to make the MusicPlaylist class implement Iterable<String>. This will allow other developers to use a for-each loop directly on your playlist object (e.g., for (String song : myPlaylist) { ... }) without giving them direct access to the underlying array.

  • Create a class MusicPlaylist that implements Iterable<String>.
  • Give it a private String[] songs field and a constructor to initialize it.
  • Implement the iterator() method. Since Arrays.asList(songs) returns a List (which is already Iterable), you can simply return the iterator from that list.
  • In your main method, instantiate a MusicPlaylist with a few songs and use a for-each loop to print each song title to the console.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.