Alle paar Monate stolper ich in irgendwelchem Code über das gleiche Problem bei der Implementierung des Observer-Patterns in Java. Deshalb hier noch mal ein kurzer Reminder, was man beachten sollte.
Häufig sieht man Implementierungen wie diese:
public class ObserverTest {
private static interface Observer {
void process();
}
private static Set observers = new HashSet();
public static void addObserver(Observer observer) {
observers.add(observer);
}
public static void removeObserver(Observer observer) {
observers.remove(observer);
}
private static void notifyObservers() {
for (Observer o : observers) {
o.process();
}
}
// es fehlt noch eine main-Methode
}
Das funktioniert so auch in vielen Fällen problemlos, in folgendem Fall aber nicht:
public static void main(String[] args) {
addObserver(new Observer() {
@Override
public void process() {
removeObserver(this);
}
});
notifyObservers();
}
Führt man das Programm so aus, wirft Java eine ConcurrentModificationException:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:761)
at java.util.LinkedList$ListItr.next(LinkedList.java:696)
at ObserverTest.notifyObservers(ObserverTest.java:21)
at ObserverTest.main(ObserverTest.java:37)
Das Problem hier ist, dass eigentlich alle Standardimplementationen von Collection keine Änderung beim iterieren zulassen. Diese Beschränkung sollte man normalerweise aber nicht an die Observer weiterreichen, da sie deren Handlungsfähigkeit stark einschränkt (ein üblicher Anwendungsfall ist, dass sich ein Observer abmeldet, wenn er die gewünschte Information erhalten hat). Zum Glück gibt es eine sehr einfache Lösung für dieses Problem: vor dem Iterieren holt man sich eine Kopie der Collection und iteriert über diese (hierbei muss beachtet werden, dass es eine “shallow copy” ist, also die Elemente selbst nicht Kopiert werden, nur der Container).
Mit folgender Modifikation läuft der Code also problemlos durch:
private static void notifyObservers() {
Set observersCopy = new HashSet(observers);
for (Observer o : observersCopy) {
o.process();
}
}