Java – for-each rettet einen

Heute sollte ich einen Fehler im fremden Code heraus finden. Das Java-Programm warf folgende Exception aus:

java.util.ConcurrentModificationException

Tja, es wurde auf jeden Fall von einer ArrayList-Collection geworfen. Meine erste Vermutung war, das zwei Threads auf dieser ArrayList schreiben. Also auf den thread-sicheren Vector umsteigen? Hat aber nichts geholfen.

Gut, also den Callstack noch mal genau durch suchen. Und siehe da, eine for-each-Schleife ist schuld. Sie arbeitet die Collection durch und irgendwo in den tiefen des Algorithmus wird ein Element aus der Collection mit remove entfernt oder mit add hinzugefügt.

Was ist passiert? Ein Beispiel:

public class InvalidIterator {
    static List<String> list = new ArrayList<>();

    public static void foo(String s) {
        System.out.println(s);
        list.remove(s);
    }

    public static void main(String[] args) {
        list.add("A");
        list.add("B");
        list.add("C");

        for (String s : list) {
            foo(s);
        }
    }
}

Die for-each-Schleife arbeitet mit Iteratoren der Collections. In Zeile 6 wird das Element s entfernt. Bei der nächsten Iteration in Zeile 14 wird ein ungültiger Iterator verwendet, weil das Element nicht mehr existiert und die weitere Iteration scheitert. BOOM!

Bei einer klassischen for-Schleife mit Indexierung:

for (int i = 0; i < list.size(); i++) {
  foo(list.get(i));
}

würde gar keine Exception geworfen werden. Achtung Falle: Es würde aber nur jedes zweite Element bearbeitet werden, da die nächsten Elemente auf den frei gewordenen Platz vorrücken und der Index zusätzlich inkrementiert!

Was ist in dem Fall besser? Natürlich die Variante mit for-each und einer Exception! Mit dem Index-Zugriff präsentiert man dem Anwender angeblich korrekte Daten. Da ist eine Exception und Abbruch ohne fehlerhafte Daten doch besser?!

Lösung

Es ist einfach nicht sinnvoll eine Collection lesend abzuarbeiten und gleichzeitig in dieser rumzufuschen, sprich die Element-Anzahl zu ändern.

Die Lösung ist einfach: man kopiert sich die Collection. In der Kopie iteriert man durch, in der originalen kann man die Element-Anzahl ändern.

public class InvalidIterator {
	static List<String> list = new ArrayList<>();
	public static void foo(String s) { 
		System.out.println(s);
		list.remove(s); 
	} 
	public static void main(String[] args) { 
		list.add("A"); 
		list.add("B"); 
		list.add("C");
		
		List<String> tempList = new ArrayList<>(list); // Kopie 
		for (String s : tempList) { // mit der Kopie arbeiten
			foo(s);
		}
	}
}