Wszyscy wiemy, że nie możesz wykonać następujących czynności z powodu ConcurrentModificationException
:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
Ale to najwyraźniej działa czasami, ale nie zawsze. Oto konkretny kod:
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);
}
To oczywiście skutkuje:
Exception in thread "main" java.util.ConcurrentModificationException
Nawet jeśli wiele wątków tego nie robi. Tak czy siak.
Jakie jest najlepsze rozwiązanie tego problemu? Jak mogę usunąć element z kolekcji w pętli bez zgłaszania tego wyjątku?
Używam tu również arbitralnego Collection
, niekoniecznie ArrayList
, więc nie możesz na nim polegać get
.
java
collections
iteration
Claudiu
źródło
źródło
Odpowiedzi:
Iterator.remove()
jest bezpieczny, możesz go użyć w następujący sposób:Pamiętaj, że
Iterator.remove()
jest to jedyny bezpieczny sposób modyfikowania kolekcji podczas iteracji; zachowanie nie jest określone, jeśli podstawowa kolekcja zostanie zmodyfikowana w jakikolwiek inny sposób podczas iteracji.Źródło: docs.oracle> Interfejs kolekcji
I podobnie, jeśli masz
ListIterator
i chcesz dodać elementy, możesz użyćListIterator#add
, z tego samego powodu, którego możesz użyćIterator#remove
- jest tak zaprojektowane, aby na to pozwolić.W twoim przypadku próbowałeś usunąć z listy, ale to samo ograniczenie obowiązuje, jeśli próbujesz przez
put
jakiśMap
czas iterować jego zawartość.źródło
iterator.next()
połączenia w pętli for? Jeśli nie, czy ktoś może wyjaśnić, dlaczego?List.add
jest w tym samym sensie „opcjonalny”, ale nie można powiedzieć, że dodanie do listy jest „niebezpieczne”.To działa:
Założyłem, że ponieważ pętla foreach to cukier syntaktyczny do iteracji, użycie iteratora nie pomogłoby ... ale daje tę
.remove()
funkcjonalność.źródło
W Javie 8 możesz użyć nowej
removeIf
metody . Dotyczy twojego przykładu:źródło
removeIf
używaIterator
iwhile
pętli. Można to zobaczyć o godzinie 8 javajava.util.Collection.java
ArrayList
przesłaniają go ze względu na wydajność. Ten, o którym mówisz, jest tylko domyślną implementacją.equals
nie jest tu w ogóle używany, więc nie trzeba go wdrażać. (Ale oczywiście, jeśli użyjeszequals
w teście, należy go zaimplementować tak, jak chcesz).Ponieważ na pytanie już udzielono odpowiedzi, tj. Najlepszym sposobem jest użycie metody remove obiektu iteratora, przejdę do szczegółów miejsca, w którym zgłaszany
"java.util.ConcurrentModificationException"
jest błąd .Każda klasa posiada prywatny zbiór klasę, która implementuje interfejs Iterator i dostarcza metod, takich jak
next()
,remove()
ihasNext()
.Kod następnego wygląda mniej więcej tak ...
Tutaj metoda
checkForComodification
jest implementowana jakoJak widać, jeśli wyraźnie spróbujesz usunąć element z kolekcji. Powoduje to,
modCount
że różni się odexpectedModCount
, co powoduje wyjątekConcurrentModificationException
.źródło
Możesz albo użyć iteratora bezpośrednio jak wspomniano, albo zachować drugą kolekcję i dodać każdy element, który chcesz usunąć, do nowej kolekcji, a następnie usunąć wszystkie na końcu. Dzięki temu możesz nadal korzystać z bezpieczeństwa typu dla każdej pętli kosztem zwiększonego zużycia pamięci i czasu procesora (nie powinno to stanowić dużego problemu, chyba że masz naprawdę duże listy lub naprawdę stary komputer)
źródło
W takich przypadkach powszechną sztuczką jest (było?) Cofanie się:
To powiedziawszy, jestem bardziej niż szczęśliwy, że masz lepsze sposoby w Javie 8, np.
removeIf
Lubfilter
w strumieniach.źródło
ArrayList
s lub podobnych kolekcji.for(int i = l.size(); i-->0;) {
?Taka sama odpowiedź jak Klaudiusz z pętlą for:
źródło
W przypadku kolekcji Eclipse metoda
removeIf
zdefiniowana w MutableCollection będzie działać:W składni Lambda Java 8 można to zapisać w następujący sposób:
Wywołanie to
Predicates.cast()
jest tutaj konieczne, ponieważremoveIf
wjava.util.Collection
interfejsie Java 8 została dodana domyślna metoda .Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse .
źródło
Utwórz kopię istniejącej listy i powtórz nową kopię.
źródło
Ludzie twierdzą, że nie można usunąć z kolekcji, która jest iterowana przez pętlę foreach. Chciałem tylko wskazać, że jest to technicznie niepoprawne i dokładnie opisać (wiem, że pytanie OP jest tak zaawansowane, że można uniknąć tego), kod stojący za tym założeniem:
Nie chodzi o to, że nie można usunąć iteracji,
Colletion
a raczej o to, że nie można kontynuować iteracji. Stądbreak
w powyższym kodzie.Przepraszam, jeśli ta odpowiedź jest nieco specjalistycznym przypadkiem użycia i bardziej pasuje do oryginalnego wątku, z którego tu przybyłem, ten jest oznaczony jako duplikat (pomimo tego, że ten wątek wydaje się bardziej niuansowy) tego i zablokowany.
źródło
Z tradycyjną pętlą for
źródło
i++
ochrony pętli, a nie w treści pętli.i++
zwiększenie nie było warunkowe - Rozumiem teraz, dlatego robisz to w ciele :)A
ListIterator
pozwala dodawać lub usuwać elementy z listy. Załóżmy, że masz listęCar
obiektów:źródło
previous
metoda.Mam sugestię dotyczącą powyższego problemu. Nie potrzeba dodatkowej listy ani dodatkowego czasu. Znajdź przykład, który zrobiłby to samo, ale w inny sposób.
Pozwoliłoby to uniknąć wyjątku dotyczącego współbieżności.
źródło
ArrayList
a zatem nie można na nim polegaćget()
. W przeciwnym razie prawdopodobnie dobre podejście.Collection
-Collection
interfejs nie obejmujeget
. (ChociażList
interfejs FWIW zawiera „get”).while
zapętlaniaList
. Ale +1 dla tej odpowiedzi, ponieważ była pierwsza.ConcurrentHashMap lub ConcurrentLinkedQueue lub ConcurrentSkipListMap może być inną opcją, ponieważ nigdy nie zgłoszą żadnego wyjątku ConcurrentModificationExap, nawet jeśli usuniesz lub dodasz element.
źródło
java.util.concurrent
pakiecie. Niektóre inne podobne / wspólne klasy przypadków użycia z tego pakietu toCopyOnWriteArrayList
&CopyOnWriteArraySet
[ale nie tylko].ConcurrentModificationException
, używanie ich w rozszerzonej pętli -for może nadal powodować problemy z indeksowaniem (tj. Nadal pomijanie elementów lubIndexOutOfBoundsException
Wiem, że to pytanie jest zbyt stare, aby dotyczyć Javy 8, ale dla tych, którzy używają Javy 8, możesz łatwo użyć removeIf ():
źródło
Innym sposobem jest utworzenie kopii tablicy arrayList:
źródło
i
nie jest obiektem typu „index
ale”. Może zadzwonienieobj
byłoby bardziej odpowiednie.źródło
W przypadku ArrayList: remove (int index) - jeśli (indeks jest pozycją ostatniego elementu) unika się go bez
System.arraycopy()
i nie zajmuje to czasu.czas arraycopy wydłuża się, gdy (indeks maleje), przy okazji maleje także elementy listy!
najlepszym skutecznym sposobem usuwania jest usuwanie jej elementów w kolejności malejącej:
while(list.size()>0)list.remove(list.size()-1);
// przyjmuje O (1)while(list.size()>0)list.remove(0);
// przyjmuje O (silnia (n))źródło
Złap to po usunięciu elementu z listy, jeśli pominiesz wewnętrzne wywołanie iterator.next (). wciąż działa! Chociaż nie proponuję pisać takiego kodu, pomaga zrozumieć koncepcję :-)
Twoje zdrowie!
źródło
Przykład modyfikacji bezpiecznego gromadzenia wątków:
źródło
Wiem, że to pytanie zakłada tylko
Collection
, a dokładniej żadneList
. Ale dla tych, którzy czytają to pytanie, którzy faktycznie pracują zList
referencją, możesz uniknąćConcurrentModificationException
za pomocąwhile
pętli (podczas modyfikowania w niej), jeśli chcesz uniknąćIterator
(albo jeśli chcesz uniknąć w ogóle, albo unikaj go konkretnie, aby osiągnąć kolejność zapętlania różni się od zatrzymania od początku do końca przy każdym elemencie [co, jak sądzę, jest jedynym, któryIterator
sam może wykonać]):* Aktualizacja: patrz komentarze poniżej wyjaśniające, że analogia jest również możliwa do osiągnięcia dzięki tradycyjnej pętli-for-loop.
Brak ConcurrentModificationException od tego kodu.
Widzimy tam, że zapętlenie nie zaczyna się od początku i nie zatrzymuje się na każdym elemencie (w co wierzę)
Iterator
nie jest w stanie zrobić).FWIW również widzimy,
get
że jest wywoływanylist
, czego nie można byłoby zrobić, gdyby jego odwołanie było po prostuCollection
(zamiast bardziej specyficznegoList
typuCollection
) -List
interfejs zawieraget
, aleCollection
interfejs nie. Gdyby nie ta różnica, wówczaslist
odniesieniem może byćCollection
[i dlatego technicznie ta odpowiedź byłaby wówczas odpowiedzią bezpośrednią, zamiast odpowiedzi stycznej].FWIWW ten sam kod nadal działa po zmodyfikowaniu, aby zaczynał od początku na stopie przy każdym elemencie (podobnie jak
Iterator
kolejność):źródło
ConcurrentModificationException
, ale nie tradycyjny -dla-loop (których inne zastosowania odpowiedź) - nie zdając sobie sprawy, że przed dlatego motywowane było napisać tę odpowiedź (ja mylnie pomyślał wtedy, że to wszystko zapętli wyjątek).Jednym z rozwiązań mogłoby być obrócenie listy i usunięcie pierwszego elementu, aby uniknąć ConcurrentModificationException lub IndexOutOfBoundsException
źródło
Spróbuj tego (usuwa wszystkie elementy z listy, które są równe
i
):źródło
może to nie być najlepszy sposób, ale w większości małych przypadków powinno to być dopuszczalne:
Nie pamiętam, skąd to przeczytałem ... dla słuszności zrobię tę wiki w nadziei, że ktoś ją znajdzie lub po prostu nie zarobię przedstawiciela, na który nie zasługuję.
źródło