Skip to content
Theory · Collections and generics · lesson 5 of 13

Iterating and immutable lists

The attendant checks the shelf of hats: walks along it and looks at each one in turn. If someone takes a hat out of the middle meanwhile, the rest slide over, and the attendant's finger lands on the wrong hat. One hat never gets looked at.

A list has the same trouble: changing it in the middle of a walk is risky. Let's start with a walk that leaves the list as it is. We count the guests with a non-empty name:

JAVA
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> guests = List.of("", "Anna", "Boris");
        int count = 0;
        for (String g : guests) {
            if (!g.isBlank()) {
                count++;
            }
        }
        System.out.println(count); // 2
    }
}

Let's go through the program line by line:

  1. List.of("", "Anna", "Boris") builds a list of three strings in one go. The first string is empty: someone opened the form and typed nothing.
  2. for (String g : guests) is the same for-each loop as for arrays. On each lap, g gets the next element of the list.
  3. !g.isBlank() skips the empty string, so the counter reaches 2.

A loop over indices works too: for (int i = 0; i < guests.size(); i++) reading guests.get(i). You need it when the element's position matters.

Removing with an iterator

Now let's remove the empty strings from the list itself. But a list from List.of cannot be changed, and we will see why in the next section. So we first copy it into an ArrayList.

An iterator can remove safely during a walk. An iterator is an object that goes through a collection element by element and can remove the one it is on. The for-each loop works through an iterator too, but a hidden one, and its remove method is out of reach from the loop. So you take the iterator yourself:

JAVA
List<String> list = new ArrayList<>(guests);
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().isBlank()) {
        it.remove();
    }
}
System.out.println(list); // [Anna, Boris]

Step by step:

  1. new ArrayList<>(guests) creates an ordinary list with the same elements.
  2. list.iterator() hands out an iterator. The type Iterator is brought in with import java.util.Iterator;.
  3. it.hasNext() answers whether elements are left, and it.next() returns the next one and moves the iterator forward.
  4. it.remove() removes the element that the last next() returned. The iterator knows about the removal itself and does not lose count.

How to remove an element from a list while walking it is a common interview question. The answer: through the iterator's remove, not the list's own remove.

Immutable lists

List.of creates an immutable list: you can read from it, but you cannot change it. In the cloakroom, it is a display case under glass: the hats are in view, but you can neither take one out nor put a new one in.

Such a list has the methods add, remove and set, since every List has them. But calling any of them, for example guests.add("Vera"), stops the program:

TEXT
Exception in thread "main" java.lang.UnsupportedOperationException

The compiler does not catch this error. It sees only the type List, and that type declares add. That the list cannot change turns out only at run time.

Immutable lists suit data that must not change: the days of the week, a list of colors, settings. If you do need to change such a list, copy it into a new ArrayList<>(…), as in the section above.

Common mistake

The empty strings are removed right inside the for-each loop:

JAVA
List<String> list = new ArrayList<>(guests);
for (String g : list) {
    if (g.isBlank()) {
        list.remove(g); // ✗
    }
}
TEXT
Exception in thread "main" java.util.ConcurrentModificationException

The list removed the empty string, but on the next lap the hidden iterator noticed that the list had been changed behind its back and threw an exception. This guards against the shelf trouble: the elements slid over, and the walk would have gone the wrong way.

Here the attendant picture is not exact: Java usually notices the slide by itself. But not always. If such a loop removes the second-to-last element, it ends early and never checks the last one. So during a walk, remove only with it.remove().

Key points

  • Walk a list with a for-each loop or a loop over indices with get(i).
  • During a walk, remove elements through an iterator: it.next() takes the element, it.remove() removes it.
  • list.remove(…) inside a for-each usually throws ConcurrentModificationException, and sometimes silently skips an element.
  • List.of creates an immutable list: add, remove and set throw UnsupportedOperationException, and new ArrayList<>(…) gives a copy you can change.