Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 24

Iterating with Iterator and Enhanced For-Each

Recommended resources

Hello! In our previous lessons, we've filled our toolbox with the fundamental data structures from the Java Collections Framework: ArrayList, LinkedList, HashSet, TreeSet, HashMap, and TreeMap. You've learned how to store and organize data using these powerful classes. Now, the next logical step is to learn how to access and process that data.

This lesson focuses on the two primary ways to traverse any collection in Java: the Iterator interface and the enhanced for-each loop. Mastering these mechanisms is crucial for a backend developer, as you will constantly need to loop through lists of database records, process sets of unique identifiers, or handle entries in configuration maps. We'll explore how these tools work, why they are designed the way they are, and when to choose one over the other.

1. A Universal Approach to Traversal: Iterable and Iterator

Imagine you have data in an ArrayList, a HashSet, and a TreeMap. Each has a different internal structure. How can you write code that accesses elements from any of them without needing to know their specific implementation details? This is the problem that Java's Iterator design pattern solves. It provides a clean, universal contract for sequential access.

The foundation for this is the java.lang.Iterable interface. Any class that implements Iterable is signaling, "You can loop over me." All the collection classes we've studied (ArrayList, HashSet, etc.) implement this interface.

The Iterable interface has one crucial abstract method: iterator(). This method returns an Iterator object. The Iterator is the actual "cursor" that you use to walk through the collection.

The video below from Programming with Mosh provides an excellent explanation of why this separation is so important and how the Iterable and Iterator interfaces work together to decouple your code from a collection's internal structure.

Java Collections Tutorial

This video masterfully explains the problem of iteration and how the Iterable interface solves it. It's a key concept for understanding good object-oriented design.

Please watch the following segments: The Problem: The instructor sets up a scenario showing why exposing a collection's internal array for iteration is a bad practice that breaks encapsulation. The Iterable Solution: This part introduces the Iterable and Iterator interfaces as the solution, explaining that Iterable promises the class can provide an Iterator. How Iterator Works: Here, you'll see how to use an Iterator with a while loop. Pay close attention to the brilliant reveal where the instructor inspects the bytecode to show that the familiar for-each loop is actually just "syntactic sugar" for this exact Iterator logic.


2. Using the Iterator

As you saw in the video, the Iterator interface has three core methods you need to know:

  • boolean hasNext(): Returns true if there are more elements to iterate over.
  • E next(): Returns the next element in the iteration and advances the cursor.
  • void remove(): Removes the last element returned by next() from the underlying collection.

This diagram illustrates the relationship. An Iterable object provides an Iterator.

A diagram showing that the `Iterable` interface has a method `iterator()` which returns an `Iterator` object. The `Iterator` then provides the `hasNext()`, `next()`, and `remove()` methods to traverse the collection.

The typical way to use an iterator is with a while loop. You get the iterator from the collection, then loop as long as hasNext() is true, processing each element you get from next().

This diagram shows how an Iterator conceptually moves through a collection. Each call to `next()` retrieves the current element and advances the internal pointer to the next one, until no elements are left.

Let's look at a practical demonstration.

Iterator Java Tutorial #68

This video from Alex Lee provides a straightforward, hands-on example of using an Iterator.

Watch these two segments to see it in action: Creating an Iterator: See how to get an Iterator from an ArrayList and call next() to get elements. The while loop: This shows the standard pattern of using while (it.hasNext()) to loop through the entire collection safely.

The code for iterating through a list of strings would look like this:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> languages = new ArrayList<>();
        languages.add("Java");
        languages.add("JavaScript");
        languages.add("Python");

        // 1. Get the iterator from the collection
        Iterator<String> iterator = languages.iterator();

        // 2. Loop using hasNext() and next()
        System.out.println("Iterating with a while loop:");
        while (iterator.hasNext()) {
            String language = iterator.next();
            System.out.println(language);
        }
    }
}

3. The Enhanced For-Each Loop

As Mosh's video revealed, the while loop with an Iterator is exactly what the Java compiler generates when you use an enhanced for-each loop. The for-each loop is a cleaner, more readable syntax for the common case of simply wanting to access each element in a collection.

Using a for-each loop, the example above becomes much more concise:

// ... same setup as before

System.out.println("\nIterating with a for-each loop:");
for (String language : languages) {
    System.out.println(language);
}

This code is easier to write and read, and it achieves the exact same result. Because it works with any object that implements Iterable, you can use this exact syntax for an ArrayList, a HashSet, or any other collection class.


4. Iterator vs. For-Each: The Critical Difference

If the for-each loop is just simpler syntax for an Iterator, why would you ever use an Iterator directly? The answer lies in one critical capability: modification.

You must use an Iterator to safely remove elements from a collection while you are iterating over it.

If you try to remove an element from a collection inside a for-each loop (or a while loop using the collection's own remove() method), you will get a ConcurrentModificationException. This happens because the iterator's internal state becomes out of sync with the collection's state. The iterator's remove() method is specifically designed to handle this by updating both the collection and its own state correctly.

The following reading and video demonstrate this crucial distinction.

Iterator vs Foreach In Java

This GeeksforGeeks article gets straight to the point, highlighting the key difference between the two iteration methods.

Please read the following sections: Start at the difference, which clearly states that you can't modify a collection in a for-each loop. Continue to the section When to use which traversal?, which provides the primary rule: use an Iterator if you need to modify the collection.

Now, let's see a practical example of safely removing items with iterator.remove().

Iterator Java Tutorial #68

This final segment from Alex Lee's video shows a perfect use case for the iterator's remove() method.

Watch the example from where he removes elements from a list of numbers that are less than 50. Notice that the remove() call happens on the iterator object, not the list itself.

Here is a code example demonstrating the right way and the wrong way to remove elements.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemovalDemo {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Cherry");
        fruits.add("Avocado");

        // SAFE REMOVAL using Iterator.remove()
        Iterator<String> iterator = fruits.iterator();
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            if (fruit.startsWith("A")) {
                iterator.remove(); // Correct!
            }
        }
        System.out.println("Fruits after safe removal: " + fruits); // [Banana, Cherry]


        // UNSAFE REMOVAL using for-each (will throw Exception)
        List<String> vegetables = new ArrayList<>();
        vegetables.add("Broccoli");
        vegetables.add("Beetroot");
        vegetables.add("Carrot");

        try {
            for (String veg : vegetables) {
                if (veg.startsWith("B")) {
                    vegetables.remove(veg); // Unsafe! Throws ConcurrentModificationException
                }
            }
        } catch (Exception e) {
            System.out.println("\nException caught: " + e);
        }
    }
}

Decision Summary:

  • Use the for-each loop for all read-only iteration. It is cleaner, simpler, and less error-prone. This will be your choice >95% of the time.
  • Use the Iterator with a while loop specifically when you need to call iterator.remove() to delete elements from the collection during the loop.

5. Iterating Over Maps

What about Map? The Map interface itself does not extend Iterable. However, it provides three methods that return Collection views, which are iterable:

  1. keySet(): Returns a Set of all the keys in the map.
  2. values(): Returns a Collection of all the values in the map.
  3. entrySet(): Returns a Set of Map.Entry<K, V> objects. Each Entry object contains both the key and the value for one pair.

Iterating over the entrySet is usually the most efficient approach if you need both the key and the value during the loop.

import java.util.HashMap;
import java.util.Map;

public class MapIterationDemo {
    public static void main(String[] args) {
        Map<String, String> capitals = new HashMap<>();
        capitals.put("India", "New Delhi");
        capitals.put("USA", "Washington D.C.");
        capitals.put("Japan", "Tokyo");

        // Most efficient way to iterate if you need both key and value
        System.out.println("Iterating over entrySet():");
        for (Map.Entry<String, String> entry : capitals.entrySet()) {
            String country = entry.getKey();
            String capital = entry.getValue();
            System.out.println("The capital of " + country + " is " + capital);
        }

        // Iterating over keys only
        System.out.println("\nIterating over keySet():");
        for (String country : capitals.keySet()) {
            System.out.println("Country: " + country);
        }

        // Iterating over values only
        System.out.println("\nIterating over values():");
        for (String capital : capitals.values()) {
            System.out.println("Capital: " + capital);
        }
    }
}

Conclusion

You now have a complete understanding of how to traverse collections in Java. You've seen that the Iterator provides a powerful, universal mechanism for accessing elements, while the for-each loop offers a convenient and readable syntax for the most common iteration tasks.

Key Takeaways:

  • The Iterable interface signals that a class can be looped over, and all standard Java collections implement it.
  • Iterable provides an Iterator, which is an object used to traverse a collection with hasNext() and next().
  • The enhanced for-each loop is syntactic sugar for using an Iterator and should be your default choice for read-only iteration.
  • To modify a collection while iterating, you must use the Iterator's remove() method to avoid a ConcurrentModificationException.
  • To iterate over a Map, you can use a for-each loop on its keySet(), values(), or entrySet().

In our next lesson, we will address a new question: now that we can iterate over collections of our own custom objects, how do we tell Java how to sort them? We will explore the Comparable and Comparator interfaces to define natural and custom ordering for our classes.