Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
47: Iterators and the Iterable Interface
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
MusicPlaylistthat implementsIterable<String>. - Give it a private
String[] songsfield and a constructor to initialize it. - Implement the
iterator()method. SinceArrays.asList(songs)returns a List (which is already Iterable), you can simply return the iterator from that list. - In your
mainmethod, instantiate aMusicPlaylistwith a few songs and use a for-each loop to print each song title to the console.
There are no comments for now.