verschieben - pack() java




Iterieren durch eine Sammlung, Vermeiden von ConcurrentModificationException beim Entfernen einer Schleife (14)

Wir alle wissen, dass Sie das nicht tun können:

for (Object i : l) {
    if (condition(i)) {
        l.remove(i);
    }
}

ConcurrentModificationException etc ... das funktioniert manchmal, aber nicht immer. Hier ist ein spezifischer Code:

public static void main(String[] args) {
    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));
    }

    for (Integer i : l) {
        if (i.intValue() == 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 Objekt in einer Schleife aus der Sammlung entfernen, ohne diese Ausnahme auszulösen?

Ich verwende hier auch eine willkürliche Collection , nicht unbedingt eine ArrayList , also kann man sich nicht auf get .


Da die Frage bereits beantwortet wurde, dh der beste Weg ist, die Methode remove des Iterator-Objekts zu verwenden, würde ich auf die Besonderheiten der Stelle "java.util.ConcurrentModificationException" an der der Fehler "java.util.ConcurrentModificationException" ausgelöst wird.

Jede Collection-Klasse hat eine private Klasse, die die Iterator-Schnittstelle implementiert und Methoden wie next() , remove() und hasNext() .

Der Code für den nächsten sieht in etwa so aus ...

public E next() {
    checkForComodification();
    try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
    } catch(IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
    }
}

Hier ist die Methode checkForComodification als implementiert

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

So, wie Sie sehen können, wenn Sie explizit versuchen, ein Element aus der Sammlung zu entfernen. modCount dass sich modCount von expectedModCount , was zur Ausnahme ConcurrentModificationException .


Dies ist möglicherweise nicht der beste Weg, aber für die meisten kleinen Fälle sollte dies akzeptabel sein:

"Erstelle ein zweites leeres Array und füge nur diejenigen hinzu, die du behalten willst"

Ich erinnere mich nicht, wo ich das gelesen habe ... wegen Rechtschaffenheit Ich werde dieses Wiki in der Hoffnung machen, dass jemand es findet oder einfach nicht Rep-verdienen, das ich nicht verdiene.


Erstellen Sie eine Kopie der vorhandenen Liste und durchlaufen Sie eine neue Kopie.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

Falls ArrayList: remove (int index) - if (der Index ist die Position des letzten Elements) wird ohne System.arraycopy() vermieden und benötigt dafür keine Zeit.

Die arraycopy-Zeit erhöht sich, wenn (der Index abnimmt), wie auch die Elemente der Liste abnehmen!

Der beste Weg zum Entfernen ist das Entfernen der 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))

//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
  • für die Indexschleife: 1090 ms
  • für desc-index: 519 ms --- das beste
  • für den Iterator: 1043 ms

Ich habe einen Vorschlag für das obige Problem. Keine Notwendigkeit einer sekundären Liste oder zusätzlicher Zeit. Finden Sie ein Beispiel, das dasselbe macht, aber auf andere Weise.

//"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.


In solchen Fällen ist ein üblicher Trick (war?) Rückwärts zu gehen:

for(int i = l.size() - 1; i >= 0; i --) {
  if (l.get(i) == 5) {
    l.remove(i);
  }
}

Das heißt, ich bin mehr als glücklich, dass Sie in Java 8 bessere Möglichkeiten haben, zB removeIf oder filter für Streams.


Mit Eclipse Collections (ehemals GS Collections ) removeIf die Methode removeIf , die in removeIf definiert ist:

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 zur Schnittstelle java.util.Collection hinzugefügt wurde.

Hinweis: Ich bin ein Committer für Eclipse Collections .


Mit Java 8 können Sie die neue Methode removeIf verwenden . Angewandt auf dein Beispiel:

Collection<Integer> coll = new ArrayList<Integer>();
//populate

coll.removeIf(i -> i.intValue() == 5);

Sie können den Iterator entweder direkt wie oben erwähnt verwenden, oder Sie können eine zweite Sammlung behalten und jedes Element, das Sie entfernen möchten, in die neue Sammlung einfügen und am Ende removeAll. Dies ermöglicht es Ihnen, die Typ-Sicherheit der for-each-Schleife auf Kosten von erhöhtem Speicherverbrauch und CPU-Zeit zu verwenden (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);
}

Sie können die Liste mit for-loop iterieren und Sie müssen list.remove (0) aufrufen. Sie müssen den Index fest mit dem Parameter remove index mit Null codieren. Siehe auch diese Antwort :

List<Integer> list = new ArrayList<Integer>();

list.add(1);
list.add(2);
list.add(3);
list.add(4);
int list_size = list.size();
for (int i = 0; i < list_size; i++) {
    list.remove(0);
}

Zusätzlich zur answer von @assylias können Sie auch die neue Stream API verwenden, wenn Sie Java 8 verwenden:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i == 5;
}

static Predicate<Integer> predicate = YourClassName::condition;

l.stream()
    .filter(predicate.negate())
    .forEach(System.out::println);

Wenn Sie die Bedingung invertieren, ist die Lösung noch prägnanter, da Sie das Prädikat nicht negate() , sodass Sie nur die Methodenreferenz verwenden können:

List<Integer> l = Arrays.asList(4, 5, 6);

static boolean condition(Integer i) {
    return i != 5;    // <-- condition has been negated
}

l.stream()
    .filter(YourClassName::condition)
    .forEach(System.out::println);

Eine der Schönheiten ist, dass der Stream träge ausgewertet wird, dh die Operation filter() wird erst ausgewertet, wenn sie von einer Terminaloperation wie forEach() . Mehr dazu finden Sie in Oracle's Tutorial .



List<String> strings=new ArrayList<String>(){};

while(strings.size() > 0) {

 String str = strings.remove(0);
}

for (Integer i : l)
{
    if (i.intValue() == 5){
            itemsToRemove.add(i);
            break;
    }
}

Der Catch 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 vorhabe, Code wie diesen zu schreiben, hilft es, das Konzept dahinter zu verstehen :-)

Prost!







collections