Bevor ich wieder in einen monatelangen Java-Beitrags-Winterschlaf falle und erneut nur über TV-Serien und alte Spiele schreibe, möchte ich hier noch einen kleinen Anfängertipp geben. Wieso eigentlich? Ganz einfach weil ich in letzter Zeit so einigen ungeübten Java-Programmierern über die Schulter sehen durfte, die alle völlig unvorbereitet in die im Folgenden thematisierte Falle tappen. Scheinbar ist Java in dieser Hinsicht wirklich so unintuitiv, dass es jedem am Anfang passiert, und ich erinnere mich, dass es mir im Studium genauso erging. Ich erkannte das Problem recht schnell und hatte auch sofort eine Lösung parat, die ich seitdem schon ganz automatisch verwende, aber vielleicht fällt das nicht jedem so leicht.
Die Situation ist schnell erklärt: Mittels Zählschleife soll über eine Collection, eine Liste, iteriert werden, die irgendwelche Elemente enthält. Diese erweiterte For-Schleife dient dazu, nicht benötigte Elemente aus der Collection zu entfernen, also eine Art Filterung soll vorgenommen werden. Am Ende verbleiben nur solche Elemente in der Collection, mit denen wir weiterarbeiten wollen. Aus Gründen der Einfachheit sind das in diesem Fall keine komplexen Datentypen, sondern Strings:
1 2 3 4 5 6 7 8 |
; html-script: false ]List<String> myList = new ArrayList<String>(); // Liste befüllen for(String curString : myList){ if(curString.contains("unavailable")) myList.remove(curString); // Bloß nicht! } |
Sieht einfach aus, ist es aber dann doch nicht. Der Code produziert nämlich eine ConcurrentModificationException. Diese Exception ist eigentlich dazu da, zu verhindern, dass eine Collection zeitgleich modifiziert wird, während gerade (z.B. in einem anderen Thread) darüber iteriert wird. Wie man sieht, braucht es allerdings gar keine anderen Threads, um die Exception zu provozieren. Wir dürfen nicht gleichzeitig iterieren und Elemente einfügen oder entfernen. Was kann ich also sonst tun, damit ich meine Elemente wegbekomme?
Eine naheliegende Lösung ist, sich die entsprechenden Elemente einfach bis dahin zu „merken“, um sie erst nach dem Iterieren zu entfernen. Alternativ könnte man sich aber auch nur die Elemente merken, die man behalten möchte, und dabei einfach eine neue Liste generieren. Welche der beiden Methoden nun besser ist, hängt davon ab, ob nach dem Entfernen mehr Elemente zurückbleiben als entfernt werden. Sowas lässt sich aber natürlich nicht immer abschätzen, darum verwende ich meistens pauschal die subtraktive Variante:
1 2 3 4 5 6 7 8 9 10 11 |
; html-script: false ]List<String> myList = new ArrayList<String>(); List<String> myDeleteList = new ArrayList<String>(); // Liste befüllen for(String curString : myList){ if(curString.contains("unavailable")) myDeleteList.add(curString); } myList.removeAll(myDeleteList); // Schon besser |
Mit der Methode removeAll() gibt es sogar gleich das ideale Werkzeug für solche Operationen. Ich benötige diesen und ähnlichen Code bei meiner Arbeit immer mal wieder. Erfahrene Java-Entwickler wissen natürlich, dass man in solchen Fällen am besten gleich einen richtigen Iterator verwendet, weil man mit diesem auf sichere Weise Elemente beim Iterieren entfernen kann. Ich bin allerdings auch gleichzeitig ein Fan der leicht verwendbaren For-Each-Schleifen, daher bin ich da meist ein wenig im Konflikt. Ist meiner Meinung nach aber auch alles kein Problem, solange man weiß, wie man damit umgehen muss.