Dlaczego nie otrzymuję wyjątku java.util.ConcurrentModificationException w tym przykładzie?

176

Uwaga: znam Iterator#remove()metodę.

W poniższym przykładzie kodu nie rozumiem, dlaczego metoda List.removein mainzgłasza ConcurrentModificationException, ale nie w removemetodzie.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}
Bhesh Gurung
źródło
3
Jedynym bezpiecznym sposobem usunięcia elementu z listy podczas iteracji po tej liście jest użycie Iterator#remove(). Dlaczego robisz to w ten sposób?
Matt Ball
@MattBall: Chciałem tylko zobaczyć, jaki może być tego powód. Ponieważ w obu metodach jest to ta sama „ulepszona pętla for”, ale jedna rzuca, ConcurrentModificationExceptiona druga nie.
Bhesh Gurung
Istnieje różnica w elemencie, który usuwasz, w metodzie usuwasz „środkowy element”. W większości usuwasz ostatnie. Jeśli zamienisz liczby, otrzymasz wyjątek w swojej metodzie. Nadal nie wiem, dlaczego tak jest.
Ben van Gompel
Miałem podobny problem, gdy moja pętla iterowała również pozycję, która nie istniała po usunięciu elementu z pętli. Po prostu naprawiłem to, dodając return;do pętli.
frank17
w systemie java8 Android usunięcie elementu innego niż ostatni spowoduje wywołanie wyjątku ConcurrentModificationException. więc w twoim przypadku funkcja remove dostałaby wyjątek, który jest odwrotny do wcześniejszego.
gonglong

Odpowiedzi:

262

Oto dlaczego: Jak jest napisane w Javadoc:

Iteratory zwracane przez iterator i metody listIterator tej klasy działają szybko: jeśli lista zostanie strukturalnie zmodyfikowana w dowolnym momencie po utworzeniu iteratora, w jakikolwiek sposób z wyjątkiem metod usuwania lub dodawania własnych iteratora, iterator zgłosi ConcurrentModificationException.

To sprawdzenie odbywa się w next()metodzie iteratora (jak widać po ścieżce stosu). Ale dotrzemy do next()metody tylko wtedy, gdy zostanie hasNext()dostarczona jako prawda, czyli to, co jest wywoływane przez for each, aby sprawdzić, czy granica jest spełniona. W twojej metodzie remove, gdy hasNext()sprawdza, czy musi zwrócić inny element, zobaczy, że zwróciła dwa elementy, a teraz po usunięciu jednego elementu lista zawiera tylko dwa elementy. Wszystko jest więc brzoskwiniowe i skończyliśmy z iteracją. Sprawdzanie współbieżnych modyfikacji nie występuje, ponieważ jest to wykonywane wnext() metodzie, która nigdy nie jest wywoływana.

Następnie przechodzimy do drugiej pętli. Po usunięciu drugiej liczby metoda hasNext ponownie sprawdzi, czy może zwrócić więcej wartości. Zwrócił już dwie wartości, ale lista zawiera teraz tylko jedną. Ale kod tutaj to:

public boolean hasNext() {
        return cursor != size();
}

1! = 2, więc kontynuujemy do next() metody, która teraz zdaje sobie sprawę, że ktoś majstrował przy liście i uruchamia wyjątek.

Mam nadzieję, że to wyjaśnia twoje pytanie.

Podsumowanie

List.remove()nie wyrzuci, ConcurrentModificationExceptiongdy usunie przedostatni element z listy.

natrętny
źródło
5
@pushy: tylko odpowiedź, która wydaje się odpowiadać temu, o co w rzeczywistości chodzi pytanie, a wyjaśnienie jest dobre. Akceptuję tę odpowiedź, a także +1. Dzięki.
Bhesh Gurung
42

Jednym ze sposobów radzenia sobie z tym jest usunięcie czegoś z kopii Collection(nie samej kolekcji), jeśli ma to zastosowanie. Cloneoryginalną kolekcję, aby wykonać kopię za pośrednictwem pliku Constructor.

Ten wyjątek może zostać zgłoszony przez metody, które wykryły współbieżną modyfikację obiektu, gdy taka modyfikacja jest niedozwolona.

W twoim konkretnym przypadku, po pierwsze, nie sądzę, aby finalbył to sposób na rozważenie, że zamierzasz zmodyfikować listę wcześniejszych deklaracji

private static final List<Integer> integerList;

Rozważ także modyfikację kopii zamiast oryginalnej listy.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}
James Raitsev
źródło
14

Metoda forward / iterator nie działa podczas usuwania elementów. Możesz usunąć element bez błędu, ale podczas próby uzyskania dostępu do usuniętych elementów wystąpi błąd w czasie wykonywania. Nie możesz użyć iteratora, ponieważ jak pokazuje pushy, spowoduje to ConcurrentModificationException, więc zamiast tego użyj zwykłej pętli for, ale przejdź przez nią wstecz.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Rozwiązanie:

Przechodź przez tablicę w odwrotnej kolejności, jeśli zamierzasz usunąć element listy. Po prostu przechodząc wstecz przez listę, unikasz odwiedzania usuniętego elementu, co usuwa wyjątek.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}
RightHandedMonkey
źródło
genialny pomysł !
dobrivoje
7

Ten fragment kodu zawsze spowoduje zgłoszenie wyjątku ConcurrentModificationException.

Reguła brzmi: „Nie możesz modyfikować (dodawać ani usuwać elementów z listy) podczas iteracji po niej za pomocą Iteratora (co ma miejsce, gdy używasz pętli for-each)”.

JavaDocs:

Iteratory zwracane przez iterator i metody listIterator tej klasy działają szybko: jeśli lista zostanie strukturalnie zmodyfikowana w dowolnym momencie po utworzeniu iteratora, w jakikolwiek sposób z wyjątkiem metod usuwania lub dodawania własnych iteratora, iterator zgłosi ConcurrentModificationException.

Dlatego jeśli chcesz zmodyfikować listę (lub ogólnie jakąkolwiek kolekcję), użyj iteratora, ponieważ wtedy jest on świadomy modyfikacji i dlatego będą one poprawnie obsługiwane.

Mam nadzieję że to pomoże.

Bhushan
źródło
3
OP wyraźnie stwierdza, że ​​jedna z pętli NIE zgłasza wyjątku, a pytający wyjaśnił, dlaczego tak się stało.
madth3
co masz na myśli mówiąc „przesłuchujący”?
Bhushan
4

Miałem ten sam problem, ale na wypadek, gdybym dodawał element en do iterowanej listy. Zrobiłem to w ten sposób

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Teraz wszystko idzie dobrze, ponieważ nie tworzysz żadnego iteratora na swojej liście, tylko iterujesz po niej „ręcznie”. I stani < integerList.size() nigdy Cię nie oszuka, ponieważ kiedy usuniesz / dodasz coś do rozmiaru listy, zmniejszaj / zwiększaj listę.

Mam nadzieję, że to pomoże, dla mnie to było rozwiązanie.

Gondil
źródło
To nie jest prawda ! Dowód: uruchom ten fragment, aby zobaczyć wynik: public static void main (String ... args) {List <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Kod zakończony"); listOfBooks.add ("Kod 22"); listOfBooks.add ("22 efektywne"); listOfBooks.add ("Netbeans 33"); System.err.println ("Przed usunięciem:" + listOfBooks); for (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Po usunięciu:" + listOfBooks); }
dobrivoje
1

Jeśli używasz kolekcji kopiowanych przy zapisie, to zadziała; jednak gdy używasz list.iterator (), zwrócony Iterator będzie zawsze odwoływał się do kolekcji elementów, tak jak to było, gdy (jak poniżej) wywołano list.iterator (), nawet jeśli inny wątek modyfikuje kolekcję. Wszelkie metody mutujące wywoływane w Iteratorze opartym na kopiowaniu przy zapisie lub w ListIteratorze (na przykład add, set lub remove) generują wyjątek UnsupportedOperationException.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}
JohnnyO
źródło
0

Działa to dobrze na Javie 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%

battosai
źródło
Przepraszam za literówkę To działa poprawnie na Javie 1.6
battosai
Hmm ... Może masz inną implementację. Ale zgodnie ze specyfikacją ma to robić, IMO. Spójrz na odpowiedź @ Pushy.
Bhesh Gurung,
niestety id nie działa na java 1.8
dobrivoje
0

W moim przypadku zrobiłem to tak:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());
Saif Hamed
źródło
0

Zmień Iterator for eachna for looprozwiązywać.

Powód jest taki:

Iteratory zwracane przez iterator i metody listIterator tej klasy działają szybko: jeśli lista zostanie strukturalnie zmodyfikowana w dowolnym momencie po utworzeniu iteratora, w jakikolwiek sposób z wyjątkiem metod usuwania lub dodawania własnych iteratora, iterator zgłosi ConcurrentModificationException.

- odesłano dokumenty Java.

Stephen
źródło
-1

Sprawdź swój kod człowieku ...

W głównej metodzie próbujesz usunąć czwarty element, którego nie ma i stąd błąd. W metodzie remove () próbujesz usunąć trzeci element, który tam jest, a zatem nie ma błędu.

Abhishek
źródło
Mylisz się: liczby 2i 3nie są indeksami listy, ale elementami. Obie logiki usuwania sprawdzają equalselementy listy, a nie indeks elementów. Ponadto, jeśli został indeks związane byłoby IndexOutOfBoundsExceptionnie ConcurrentModificationException.
Malte Hartwig,