Wir alle wissen, dass Sie Folgendes nicht tun können, weil ConcurrentModificationException
:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
Aber das funktioniert anscheinend manchmal, aber nicht immer. Hier ist ein spezifischer Code:
public static void main(String[] args) {
Collection<Integer> l = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Dies führt natürlich zu:
Exception in thread "main" java.util.ConcurrentModificationException
Auch wenn mehrere Threads dies nicht tun. Wie auch immer.
Was ist die beste Lösung für dieses Problem? Wie kann ich ein Element in einer Schleife aus der Sammlung entfernen, ohne diese Ausnahme auszulösen?
Ich benutze Collection
hier auch eine beliebige , nicht unbedingt eine ArrayList
, also kann man sich nicht darauf verlassen get
.
java
collections
iteration
Claudiu
quelle
quelle
Antworten:
Iterator.remove()
ist sicher, Sie können es so verwenden:Beachten Sie, dass dies
Iterator.remove()
der einzig sichere Weg ist, eine Sammlung während der Iteration zu ändern. Das Verhalten ist nicht angegeben, wenn die zugrunde liegende Sammlung während der Iteration auf andere Weise geändert wird.Quelle: docs.oracle> Die Sammlungsschnittstelle
Wenn Sie ein Element haben
ListIterator
und Elemente hinzufügen möchten, können Sie esListIterator#add
aus demselben Grund verwenden , aus dem Sie es verwenden könnenIterator#remove
- es ist so konzipiert, dass es dies zulässt.In Ihrem Fall haben Sie versucht aus einer Liste zu entfernen, aber die gleiche Einschränkung gilt für , wenn versucht ,
put
eine inMap
während sein Inhalt iterieren.quelle
iterator.next()
Aufruf in die for-Schleife zu setzen? Wenn nicht, kann jemand erklären, warum?List.add
ist in diesem Sinne auch "optional", aber Sie würden nicht sagen, dass es "unsicher" ist, einer Liste hinzuzufügen.Das funktioniert:
Ich nahm an, dass die Verwendung eines Iterators nicht helfen würde, da eine foreach-Schleife syntaktischer Zucker für die Iteration ist ... aber sie bietet Ihnen diese
.remove()
Funktionalität.quelle
Mit Java 8 können Sie die neue
removeIf
Methode verwenden . Auf Ihr Beispiel angewendet:quelle
removeIf
verwendetIterator
undwhile
Schleife. Sie können es bei Java 8java.util.Collection.java
ArrayList
überschreiben es aus Leistungsgründen. Diejenige, auf die Sie sich beziehen, ist nur die Standardimplementierung.equals
wird hier überhaupt nicht verwendet, muss also nicht implementiert werden. (Aber natürlich, wenn Sieequals
in Ihrem Test verwenden, dann muss es so implementiert werden, wie Sie es wollen.)Da die Frage bereits beantwortet wurde, dh der beste Weg ist, die Methode remove des Iteratorobjekts zu verwenden, würde ich auf die Besonderheiten des Ortes eingehen, an dem der Fehler
"java.util.ConcurrentModificationException"
ausgelöst wird.Jede Kollektion Klasse verfügt über eine eigene Klasse , die den Iterator - Schnittstelle implementiert und stellt Methoden wie
next()
,remove()
undhasNext()
.Der Code für next sieht ungefähr so aus ...
Hier ist die Methode
checkForComodification
implementiert alsWie Sie sehen, versuchen Sie explizit, ein Element aus der Sammlung zu entfernen. Dies führt dazu,
modCount
dass Sie sich von unterscheidenexpectedModCount
, was zur Ausnahme führtConcurrentModificationException
.quelle
Sie können den Iterator entweder direkt wie erwähnt verwenden oder eine zweite Sammlung behalten und jedes Element, das Sie entfernen möchten, zur neuen Sammlung hinzufügen und am Ende alles entfernen. Auf diese Weise können Sie die Typensicherheit der for-each-Schleife auf Kosten einer erhöhten Speichernutzung und CPU-Zeit weiter nutzen (sollte kein großes Problem sein, es sei denn, Sie haben wirklich, wirklich große Listen oder einen wirklich alten Computer).
quelle
In solchen Fällen ist (war?) Ein häufiger Trick, rückwärts zu gehen:
Trotzdem bin ich mehr als froh, dass Sie in Java 8 bessere Möglichkeiten haben, z . B.
removeIf
oderfilter
in Streams.quelle
ArrayList
s oder ähnliche Sammlungen.for(int i = l.size(); i-->0;) {
?Gleiche Antwort wie Claudius mit einer for-Schleife:
quelle
Bei Eclipse-Sammlungen funktioniert
removeIf
die in MutableCollection definierte Methode :Mit der Java 8 Lambda-Syntax kann dies wie folgt geschrieben werden:
Der Aufruf von
Predicates.cast()
ist hier erforderlich, daremoveIf
derjava.util.Collection
Schnittstelle in Java 8 eine Standardmethode hinzugefügt wurde .Hinweis: Ich bin ein Committer für Eclipse-Sammlungen .
quelle
Erstellen Sie eine Kopie der vorhandenen Liste und durchlaufen Sie die neue Kopie.
quelle
Die Leute behaupten, man könne nicht aus einer Sammlung entfernen, die von einer foreach-Schleife iteriert wird. Ich wollte nur darauf hinweisen, dass dies technisch nicht korrekt ist, und den Code hinter dieser Annahme genau beschreiben (ich weiß, dass die Frage des OP so weit fortgeschritten ist, dass ich dies nicht weiß):
Es ist nicht so, dass Sie nicht aus der
Colletion
Iteration entfernen können, sondern dass Sie die Iteration nicht fortsetzen können, wenn Sie dies tun. Daher dasbreak
im obigen Code.Entschuldigung, wenn diese Antwort ein etwas spezieller Anwendungsfall ist und besser zu dem ursprünglichen Thread passt, von dem ich hierher gekommen bin, ist dieser als Duplikat (obwohl dieser Thread nuancierter erscheint) davon markiert und gesperrt.
quelle
Mit einer traditionellen for-Schleife
quelle
i++
im Schleifenschutz und nicht im Schleifenkörper zu erhöhen .i++
Inkrementieren nicht bedingt wäre - ich sehe jetzt, deshalb machst du es im Körper :)Mit A
ListIterator
können Sie Elemente zur Liste hinzufügen oder daraus entfernen. Angenommen, Sie haben eine Liste vonCar
Objekten:quelle
previous
Methode.Ich habe einen Vorschlag für das obige Problem. Keine Notwendigkeit für eine sekundäre Liste oder zusätzliche Zeit. Bitte finden Sie ein Beispiel, das das Gleiche tut, aber auf andere Weise.
Dies würde die Parallelitätsausnahme vermeiden.
quelle
ArrayList
und sich daher nicht darauf verlassen kannget()
. Ansonsten aber wahrscheinlich ein guter Ansatz.Collection
-Collection
Schnittstelle enthält nichtget
. (Obwohl die FWIW-List
Schnittstelle 'get' enthält).while
-looping aList
. Aber +1 für diese Antwort, weil sie zuerst kam.ConcurrentHashMap oder ConcurrentLinkedQueue oder ConcurrentSkipListMap können eine weitere Option sein, da sie niemals eine ConcurrentModificationException auslösen, selbst wenn Sie ein Element entfernen oder hinzufügen.
quelle
java.util.concurrent
Paket sind. Einige andere ähnliche / häufig verwendete Anwendungsklassen aus diesem Paket sindCopyOnWriteArrayList
&CopyOnWriteArraySet
[aber nicht auf diese beschränkt].ConcurrentModificationException
, so dass sie in einem mit verbessertem -für-Schleife kann noch Ursache Indizierungsprobleme (zB: nach wie vor Elemente überspringen, oderIndexOutOfBoundsException
...)Ich weiß, dass diese Frage zu alt ist, um sich mit Java 8 zu befassen, aber für diejenigen, die Java 8 verwenden, können Sie removeIf () einfach verwenden:
quelle
Eine andere Möglichkeit besteht darin, eine Kopie Ihrer ArrayList zu erstellen:
quelle
i
ist nicht einindex
sondern das Objekt. Vielleichtobj
wäre es passender , es zu nennen.quelle
Falls ArrayList: remove (int index) - if (index ist die Position des letzten Elements) vermeidet es ohne
System.arraycopy()
und benötigt dafür keine Zeit.Die Array-Kopierzeit erhöht sich, wenn (der Index nimmt ab), übrigens auch die Elemente der Liste!
Der effektivste Weg zum Entfernen ist das Entfernen seiner Elemente in absteigender Reihenfolge:
while(list.size()>0)list.remove(list.size()-1);
// nimmt O (1)while(list.size()>0)list.remove(0);
// nimmt O (Fakultät (n))quelle
Der Haken ist der nach dem Entfernen des Elements aus der Liste, wenn Sie den internen Aufruf von iterator.next () überspringen. es funktioniert noch! Obwohl ich nicht vorschlage, Code wie diesen zu schreiben, hilft es, das Konzept dahinter zu verstehen :-)
Prost!
quelle
Beispiel für eine Änderung der thread-sicheren Sammlung:
quelle
Ich weiß, dass diese Frage nur eine
Collection
und keine spezifischere voraussetztList
. Aber für diejenigen, die diese Frage lesen und tatsächlich mit einerList
Referenz arbeiten, können Sie sie stattdessenConcurrentModificationException
mit einerwhile
Schleife vermeiden (während Sie sie ändern), wenn Sie dies vermeiden möchtenIterator
(entweder wenn Sie sie generell vermeiden möchten oder wenn Sie sie speziell vermeiden möchten, um sie zu erreichen) eine Schleifenreihenfolge, die sich vom Anhalten von Anfang bis Ende bei jedem Element unterscheidet [was meiner Meinung nach die einzige Reihenfolge ist, dieIterator
selbst ausgeführt werden kann]):* Update: Siehe Kommentare unten, die verdeutlichen, dass das Analoge auch mit der herkömmlichen -for-Schleife erreichbar ist.
Keine ConcurrentModificationException von diesem Code.
Dort sehen wir, dass die Schleife nicht am Anfang beginnt und nicht bei jedem Element aufhört (was ich glaube
Iterator
selbst nicht möglich ist).FWIW wird auch
get
aufgerufenlist
, was nicht möglich wäre, wenn die Referenz nurCollection
(anstelle des spezifischerenList
Typs vonCollection
) wäre -List
Schnittstelle enthältget
,Collection
Schnittstelle jedoch nicht. Ohne diesen Unterschiedlist
könnte die Referenz stattdessen eineCollection
[und daher wäre diese Antwort technisch gesehen eine direkte Antwort anstelle einer tangentialen Antwort] sein.FWIWW Der gleiche Code funktioniert auch nach der Änderung so, dass er bei jedem Element am Anfang beginnt (genau wie bei der
Iterator
Reihenfolge):quelle
ConcurrentModificationException
, aber nicht die traditionelle -for-Schleife (die in der anderen Antwort verwendet wird) dachte dann, dass es alles for-Schleifen waren, die die Ausnahme auslösen würden).Eine Lösung könnte darin bestehen, die Liste zu drehen und das erste Element zu entfernen, um die ConcurrentModificationException oder IndexOutOfBoundsException zu vermeiden
quelle
Versuchen Sie dies (entfernt alle Elemente in der Liste, die gleich sind
i
):quelle
Sie können auch Rekursion verwenden
Rekursion in Java ist ein Prozess, bei dem sich eine Methode kontinuierlich aufruft. Eine Methode in Java, die sich selbst aufruft, wird als rekursive Methode bezeichnet.
quelle
Dies ist möglicherweise nicht der beste Weg, aber für die meisten kleinen Fälle sollte dies akzeptabel sein:
Ich erinnere mich nicht, woher ich das gelesen habe ... aus Gründen der Gerechtigkeit werde ich dieses Wiki erstellen, in der Hoffnung, dass jemand es findet oder nur um keine Wiederholung zu verdienen, die ich nicht verdiene.
quelle