Wir alle wissen, dass Sie das nicht können:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
ConcurrentModificationException
etc ... das funktioniert anscheinend manchmal, aber nicht immer. Hier ist ein spezieller 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
... obwohl mehrere Threads es 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 verwende hier auch ein willkürliches Collection
, nicht unbedingt ein ArrayList
, sodass Sie sich nicht auf get
verlassen können.
Iterator.remove()
ist sicher, du kannst es so benutzen:
_List<String> list = new ArrayList<>();
// This is a clever way to create the iterator and call iterator.hasNext() like
// you would do in a while-loop. It would be the same as doing:
// Iterator<String> iterator = list.iterator();
// while (iterator.hasNext()) {
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
String string = iterator.next();
if (string.isEmpty()) {
// Remove the current element from the iterator and the list.
iterator.remove();
}
}
_
Beachten Sie, dass Iterator.remove()
die einzig sichere Möglichkeit ist, eine Sammlung während der Iteration zu ändern. Das Verhalten ist nicht angegeben, wenn die zugrunde liegende Auflistung während der Iteration auf andere Weise geändert wird .
Quelle: docs.Oracle> The Collection Interface
Und in ähnlicher Weise können Sie ListIterator#add
verwenden, wenn Sie ein ListIterator
haben und hinzufügen möchten. Aus demselben Grund können Sie _Iterator#remove
_ - es wurde entwickelt, um es zu ermöglichen.
In Ihrem Fall haben Sie versucht, aus einer Liste zu entfernen, aber die gleiche Einschränkung gilt, wenn Sie versuchen, put
in Map
zu ändern, während Sie den Inhalt durchlaufen.
Das funktioniert:
Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
if (iter.next() == 5) {
iter.remove();
}
}
Ich bin davon ausgegangen, dass die Verwendung eines Iterators nicht hilfreich ist, da eine foreach-Schleife syntaktischer Zucker zum Iterieren ist ... aber es gibt Ihnen diese .remove()
-Funktionalität.
Mit Java 8 können Sie die neue removeIf
Methode verwenden. Angewandt auf Ihr Beispiel:
Collection<Integer> coll = new ArrayList<>();
//populate
coll.removeIf(i -> i == 5);
Da die Frage bereits beantwortet wurde, d. H. Der beste Weg ist, die Entfernungsmethode des Iteratorobjekts zu verwenden, würde ich auf die Einzelheiten der Stelle eingehen, an der der Fehler "Java.util.ConcurrentModificationException"
ausgegeben wird.
Jede Auflistungsklasse verfügt über eine private Klasse, die die Iterator-Schnittstelle implementiert und Methoden wie next()
, remove()
und hasNext()
bereitstellt.
Der Code für next sieht ungefähr so aus ...
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Hier wird die Methode checkForComodification
implementiert als
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Sie sehen also, wenn Sie explizit versuchen, ein Element aus der Auflistung zu entfernen. Dies führt dazu, dass modCount
sich von expectedModCount
unterscheidet, was zur Ausnahme ConcurrentModificationException
führt.
Sie können entweder den Iterator direkt wie erwähnt verwenden oder eine zweite Sammlung führen und jedes Element, das Sie entfernen möchten, zur neuen Sammlung hinzufügen und dann am Ende alle Elemente entfernen. Auf diese Weise können Sie die Typensicherheit der for-each-Schleife auf Kosten des erhöhten Speicherverbrauchs und der CPU-Zeit weiter nutzen (sollte kein großes Problem sein, es sei denn, Sie haben wirklich große Listen oder einen wirklich alten Computer).
public static void main(String[] args)
{
Collection<Integer> l = new ArrayList<Integer>();
Collection<Integer> itemsToRemove = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l)
{
if (i.intValue() == 5)
itemsToRemove.add(i);
}
l.removeAll(itemsToRemove);
System.out.println(l);
}
In solchen Fällen ist (war?) Ein häufiger Trick, rückwärts zu gehen:
for(int i = l.size() - 1; i >= 0; i --) {
if (l.get(i) == 5) {
l.remove(i);
}
}
Trotzdem bin ich mehr als glücklich, dass Sie in Java 8 bessere Möglichkeiten haben, z. removeIf
oder filter
in Streams.
Gleiche Antwort wie Claudius mit einer for-Schleife:
for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
Object object = it.next();
if (test) {
it.remove();
}
}
Mit Eclipse Collections (früher GS Collections ) funktioniert die Methode removeIf
, die am MutableCollection definiert wurde:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.lessThan(3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
Mit Java 8 Lambda-Syntax kann dies wie folgt geschrieben werden:
MutableList<Integer> list = Lists.mutable.of(1, 2, 3, 4, 5);
list.removeIf(Predicates.cast(integer -> integer < 3));
Assert.assertEquals(Lists.mutable.of(3, 4, 5), list);
Der Aufruf von Predicates.cast()
ist hier erforderlich, da in Java 8 eine Standardmethode removeIf
für die Schnittstelle Java.util.Collection
hinzugefügt wurde.
Hinweis: Ich bin ein Committer für Eclipse Collections .
Erstellen Sie eine Kopie der vorhandenen Liste und iterieren Sie über eine neue Kopie.
for (String str : new ArrayList<String>(listOfStr))
{
listOfStr.remove(/* object reference or index */);
}
Mit einer traditionellen for-Schleife
ArrayList<String> myArray = new ArrayList<>();
for (int i = 0; i < myArray.size(); ) {
String text = myArray.get(i);
if (someCondition(text))
myArray.remove(i);
else
i++;
}
Leute behaupten, man könne nicht aus einer Sammlung entfernen, die von einer foreach-Schleife durchlaufen wird. Ich wollte nur darauf hinweisen, dass technisch inkorrekt ist, und genau beschreiben (ich weiß, dass die Frage des OP so weit fortgeschritten ist, dass ich das nicht weiß), welcher Code hinter dieser Annahme steckt:
for (TouchableObj obj : untouchedSet) { // <--- This is where ConcurrentModificationException strikes
if (obj.isTouched()) {
untouchedSet.remove(obj);
touchedSt.add(obj);
break; // this is key to avoiding returning to the foreach
}
}
Es ist nicht so, dass Sie nicht aus dem iterierten Colletion
entfernen können, sondern dass Sie die Iteration nicht fortsetzen können, wenn Sie dies tun. Daher das break
im obigen Code.
Entschuldigung, wenn diese Antwort ein etwas speziellerer Anwendungsfall ist und besser zum Original passt Thread Ich bin hierher gekommen, dass einer als Duplikat (obwohl dieser Thread nuancierter erscheint) von diesem markiert und gesperrt ist.
Mit ListIterator
können Sie Elemente zur Liste hinzufügen oder daraus entfernen. Angenommen, Sie haben eine Liste von Car
Objekten:
List<Car> cars = ArrayList<>();
// add cars here...
for (ListIterator<Car> carIterator = cars.listIterator(); carIterator.hasNext(); )
{
if (<some-condition>)
{
carIterator().remove()
}
else if (<some-other-condition>)
{
carIterator().add(aNewCar);
}
}
Eine andere Möglichkeit besteht darin, eine Kopie Ihrer ArrayList zu erstellen:
List<Object> l = ...
List<Object> iterationList = ImmutableList.copyOf(l);
for (Object i : iterationList) {
if (condition(i)) {
l.remove(i);
}
}
Der beste Weg (empfohlen) ist die Verwendung des Java.util.Concurrent-Pakets. Durch die Verwendung dieses Pakets können Sie diese Ausnahme auf einfache Weise vermeiden. Siehe Modifizierter Code
public static void main(String[] args) {
Collection<Integer> l = new CopyOnWriteArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
for (Integer i : l) {
if (i.intValue() == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Ich habe einen Vorschlag für das obige Problem. Keine sekundäre Liste oder zusätzliche Zeit erforderlich. Finden Sie ein Beispiel, das dasselbe, aber auf andere Weise ausführt.
//"list" is ArrayList<Object>
//"state" is some boolean variable, which when set to true, Object will be removed from the list
int index = 0;
while(index < list.size()) {
Object r = list.get(index);
if( state ) {
list.remove(index);
index = 0;
continue;
}
index += 1;
}
Dies würde die Nebenläufigkeitsausnahme vermeiden.
ConcurrentHashMap oder ConcurrentLinkedQueue oder ConcurrentSkipListMap sind möglicherweise eine weitere Option, da sie niemals eine ConcurrentModificationException auslösen, selbst wenn Sie Elemente entfernen oder hinzufügen.
Ich weiß, dass diese Frage nur von einem Collection
ausgeht und nicht von einem List
. Aber für diejenigen, die diese Frage lesen und tatsächlich mit einer List
-Referenz arbeiten, können Sie ConcurrentModificationException
mit einer while
- -Schleife vermeiden (während Sie darin Änderungen vornehmen) wenn Sie Iterator
vermeiden möchten (entweder wenn Sie es im Allgemeinen vermeiden möchten oder wenn Sie es im Besonderen vermeiden möchten, um eine andere Schleifenreihenfolge als das durchgehende Anhalten an jedem Element zu erreichen [was ich glaube] der einzige Befehl, den Iterator
selbst ausführen kann]):
* Update: Siehe unten stehende Kommentare, die verdeutlichen, dass das Analoge auch mit der traditionellen - for-Schleife erreichbar ist.
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 1;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i++);
} else {
i += 2;
}
}
Keine ConcurrentModificationException von diesem Code.
Dort sehen wir, dass die Schleife nicht am Anfang beginnt und nicht bei jedem Element endet (was meines Erachtens Iterator
selbst nicht kann). .
FWIW sehen wir auch, dass get
für list
aufgerufen wird, was nicht möglich wäre, wenn sein Verweis nur Collection
wäre (anstelle des spezifischeren List
- Typs) der Schnittstelle Collection
) - List
enthält get
, die Schnittstelle Collection
jedoch nicht. Ohne diesen Unterschied könnte die Referenz list
stattdessen eine Collection
[sein, und daher wäre diese Antwort technisch eine direkte Antwort anstelle einer tangentialen Antwort].
FWIWW-Code funktioniert nach der Änderung immer noch, sodass er bei jedem Element am Anfang und am Ende beginnt (genau wie bei Iterator
order):
final List<Integer> list = new ArrayList<>();
for(int i = 0; i < 10; ++i){
list.add(i);
}
int i = 0;
while(i < list.size()){
if(list.get(i) % 2 == 0){
list.remove(i);
} else {
++i;
}
}
Eine Lösung könnte darin bestehen, die Liste zu drehen und das erste Element zu entfernen, um die ConcurrentModificationException oder die IndexOutOfBoundsException zu vermeiden
int n = list.size();
for(int j=0;j<n;j++){
//you can also put a condition before remove
list.remove(0);
Collections.rotate(list, 1);
}
Collections.rotate(list, -1);
for (Integer i : l)
{
if (i.intValue() == 5){
itemsToRemove.add(i);
break;
}
}
Der Haken ist das nach dem Entfernen des Elements aus der Liste, wenn Sie den internen iterator.next () -Aufruf überspringen. es funktioniert noch! Obwohl ich nicht vorschlage, Code wie diesen zu schreiben, hilft es, das Konzept dahinter zu verstehen :-)
Prost!
Beispiel für eine Änderung der thread-sicheren Sammlung:
public class Example {
private final List<String> queue = Collections.synchronizedList(new ArrayList<String>());
public void removeFromQueue() {
synchronized (queue) {
Iterator<String> iterator = queue.iterator();
String string = iterator.next();
if (string.isEmpty()) {
iterator.remove();
}
}
}
}
Ich weiß, dass diese Frage zu alt für Java 8 ist, aber für diejenigen, die Java 8 verwenden, können Sie einfach removeIf () verwenden:
Collection<Integer> l = new ArrayList<Integer>();
for (int i=0; i < 10; ++i) {
l.add(new Integer(4));
l.add(new Integer(5));
l.add(new Integer(6));
}
l.removeIf(i -> i.intValue() == 5);
Falls ArrayList: remove (int index) - falls (index die Position des letzten Elements ist), wird dies ohne System.arraycopy()
vermieden und es wird dafür keine Zeit benötigt.
die ArrayCopy-Zeit nimmt zu, wenn (der Index abnimmt). Übrigens nehmen auch die Elemente der Liste ab!
die beste Methode zum Entfernen besteht darin, die Elemente in absteigender Reihenfolge zu entfernen: while(list.size()>0)list.remove(list.size()-1);
// nimmt O(1) while(list.size()>0)list.remove(0);
// nimmt O (Fakultativ (n))
//region prepare data
ArrayList<Integer> ints = new ArrayList<Integer>();
ArrayList<Integer> toRemove = new ArrayList<Integer>();
Random rdm = new Random();
long millis;
for (int i = 0; i < 100000; i++) {
Integer integer = rdm.nextInt();
ints.add(integer);
}
ArrayList<Integer> intsForIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsDescIndex = new ArrayList<Integer>(ints);
ArrayList<Integer> intsIterator = new ArrayList<Integer>(ints);
//endregion
// region for index
millis = System.currentTimeMillis();
for (int i = 0; i < intsForIndex.size(); i++)
if (intsForIndex.get(i) % 2 == 0) intsForIndex.remove(i--);
System.out.println(System.currentTimeMillis() - millis);
// endregion
// region for index desc
millis = System.currentTimeMillis();
for (int i = intsDescIndex.size() - 1; i >= 0; i--)
if (intsDescIndex.get(i) % 2 == 0) intsDescIndex.remove(i);
System.out.println(System.currentTimeMillis() - millis);
//endregion
// region iterator
millis = System.currentTimeMillis();
for (Iterator<Integer> iterator = intsIterator.iterator(); iterator.hasNext(); )
if (iterator.next() % 2 == 0) iterator.remove();
System.out.println(System.currentTimeMillis() - millis);
//endregion