Java: Element aus List entfernen mit einer Schleife


Auf folgende Problemstellung stößt man doch immer wieder: Man muss ein Element aus einer Liste entfernen, welches bestimmten Kriterien entspricht. Jedoch kann man beim Löschen von Elementen aus einer Liste schnell in eine Falle tappen, indem man mit einfachen Schleifenkonstruktionen die Threadsicherheit aufs Spiel setzt. Wie möchte ich in folgenden Sourcen aufzeigen:

Es bietet sich, um die Elemente einer Liste zu überprüfen, die erweiterte for Schleife an. Sie ist gut zu lesen, einfach zu programmieren, man bekommt jedes einzelne Element als Referenz und muss nicht selbst auf den Index achten.

for (Element e : liste){
	if (e.read().equals(“ja”) { ... };
}

Nutzt man dieses Konstrukt um ein Element der Liste ausfindig zu machen, unterwandert man beim Löschen die Integrität des Listen-Objekts und Java wirft eine ConcurrentModificationException. Dabei ist noch nichtmals mehr als ein Thread notwendig, wie im folgenden Beispiel gezeigt. Der Aufruf erscheint wie von einem anderen Thread. Das Element wird zwar gelöscht, jedoch beim Versuch auf das nächste Element zu wechseln tritt  ein Fehler auf und das zweite Objekt das den Kriterien entsprochen hat wird nicht mehr aus der Liste entfernt.

import java.util.ArrayList;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.List;

public class RemoveFromListTest {

	private List test = new ArrayList();

	private void init() {
		test.add("start");
		test.add("in");
		test.add("the");
		test.add("middel");
		test.add("of");
		test.add("the");
		test.add("array");
		test.add("end");
	}

	private void printArray() {
		System.out.println(test);
	}

	private void remove(Object toRemove) {
		try {
			for (String a : test) {
				if (a.equals(toRemove)) {
					test.remove(a);
				}
			}
		} catch (ConcurrentModificationException e) {
			System.out.println("Exception when deleting \""
					+ toRemove.toString() + "\"");
		}
	}

	public static void main(String[] args) {
		RemoveFromListTest testList = new RemoveFromListTest();
		testList.init();
		testList.printArray();
		testList.remove("in");
		testList.printArray();
		testList.remove("start");
		testList.printArray();
		testList.remove("end");
		testList.printArray();

	}
}

Die Ausgabe:

[start, in, in, the, middel, of, the, array, end]
Exception when deleting „in“
[start, in, the, middel, of, the, array, end]
Exception when deleting „start“
[in, the, middel, of, the, array, end]
Exception when deleting „end“
[in, the, middel, of, the, array]

Erste Intention das zu Lösen ist es vielleicht sich die zu löschenden Objekte zu merken und später nach der Schleife aus der Liste zu nehmen. Die saubere Lösung für diese Fehlerquelle ist jedoch ein Iterator. Mit ihm können wir das Element direkt zum Zeitpunkt des Auffindens entfernen.

for (Iterator iterator = this.test.iterator(); iterator.hasNext();) {
	String a = iterator.next();
	if (a.equals(toRemove)) {
		iterator.remove();
	}
}

Somit läuft man nicht mehr offen in eine Exception und muss sich die zu löschenden Objekte auch nirgends extra merken, was zu übersichtlicherem Code führt.

Na denn, schaut nach wie ihr sowas schon in alten Projekten gelöst habt! Könnt es auch gerne unten in die Kommentare stellen.

Greets!

Mehr Informationen

zum Iterator im Galileo Computing Open Book: Java ist auch eine Insel

Die ConcurrentModificationException beschrieben in der Java API

Ähnliche Artikel:

5 Kommentare

  1. Danke für das Tutorial. Aber die Zeile String a = iterator.next(); gibt bei mir den Fehler:
    Type mismatch: cannot convert from Object to
    String. Auch wenn ich das Objekt caste, wills nicht laufen….

      1. ok wurde durch den HTML-Parser ad absurdum geführt, neuer Versuch:

        Iterator, durch
        Iterator<String>

        ersetzen. (Für den Fall, dass es nicht geklappt hat: dem Iterator den Typ String in spitzen Klammern angeben)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

*