Iteracja poprzez kolekcję, unikanie ConcurrentModificationException podczas usuwania w pętli
Wszyscy wiemy, że nie możesz tego zrobić:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
ConcurrentModificationException
itd... to najwyraźniej działa czasami, ale nie zawsze. Oto jakiś konkretny kod:
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);
}
To oczywiście skutkuje:
Exception in thread "main" java.util.ConcurrentModificationException
... mimo że wiele wątków tego nie robi... W każdym razie.
Jakie jest najlepsze rozwiązanie tego problemu? Jak mogę usunąć element z kolekcji w pętli bez rzucania tego wyjątku?
Używam również arbitralnego Collection
tutaj, niekoniecznie ArrayList
, więc ty nie można polegać na get
.
23 answers
Iterator.remove()
jest bezpieczny, można go używać w ten sposób:
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();
}
}
Zauważ, że Iterator.remove()
jest jedynym bezpiecznym sposobem modyfikacji kolekcji podczas iteracji; zachowanie jest nieokreślone, jeśli bazowa kolekcja jest modyfikowana w jakikolwiek inny sposób podczas iteracji jest w toku.
Źródło: docs.oracle > the Collection Interface
I podobnie, jeśli masz ListIterator
i chcesz dodać elementy, możesz użyć ListIterator#add
, z tego samego powodu, dla którego ty może używać Iterator#remove
- jest zaprojektowany, aby na to pozwolić.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-20 13:18:12
To działa:
Iterator<Integer> iter = l.iterator();
while (iter.hasNext()) {
if (iter.next().intValue() == 5) {
iter.remove();
}
}
Założyłem, że ponieważ pętla foreach jest cukrem składniowym do iteracji, użycie iteratora nie pomoże... ale daje Ci to .remove()
funkcjonalność.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-19 12:57:25
W Javie 8 możesz użyć nowej metody removeIf
. Zastosowany do twojego przykładu:
Collection<Integer> coll = new ArrayList<Integer>();
//populate
coll.removeIf(i -> i.intValue() == 5);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-28 10:23:22
Ponieważ pytanie zostało już udzielone, tzn. najlepszym sposobem jest użycie metody remove obiektu iteratora, przejdę do specyfiki miejsca, w którym wyrzucany jest błąd "java.util.ConcurrentModificationException"
.
Każda klasa kolekcji ma klasę prywatną, która implementuje interfejs iteratora i dostarcza metody takie jak next()
, remove()
i hasNext()
.
Kod do next wygląda mniej więcej tak...
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
Tutaj metoda checkForComodification
jest zaimplementowana jako
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Tak, jak ty można zobaczyć, jeśli jawnie próbujesz usunąć element z kolekcji. Powoduje to, że modCount
różni się od expectedModCount
, w wyniku czego powstaje wyjątek ConcurrentModificationException
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-19 10:22:32
Możesz użyć iteratora bezpośrednio, jak wspomniałeś, albo zachować drugą kolekcję i dodać każdy element, który chcesz usunąć do nowej kolekcji, a następnie removeAll na końcu. Pozwala to na zachowanie bezpieczeństwa typu pętli for-each kosztem zwiększonego zużycia pamięci i czasu procesora (nie powinno to być wielkim problemem, chyba że masz naprawdę duże listy lub naprawdę stary komputer)
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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-21 23:32:17
W takich przypadkach powszechnym trikiem jest (was?) do tyłu:
for(int i = l.size() - 1; i >= 0; i --) {
if (l.get(i) == 5) {
l.remove(i);
}
}
To powiedziawszy, Jestem bardziej niż szczęśliwy, że masz lepsze sposoby w Javie 8, np. removeIf
lub filter
na streamach.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-29 09:56:15
Ta sama odpowiedź co Klaudiusz z pętlą for:
for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
Object object = it.next();
if (test) {
it.remove();
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:36
Z kolekcjami Eclipse (dawniej kolekcjami GS ), Metoda removeIf
zdefiniowana na MutableCollection będzie działać:
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);
W składni Java 8 Lambda można to zapisać w następujący sposób:
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);
Wywołanie Predicates.cast()
jest tutaj konieczne, ponieważ domyślna metoda removeIf
została dodana w interfejsie java.util.Collection
W Java 8.
Uwaga: jestem committerem dla kolekcji Eclipse .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-01 00:56:39
Wykonaj kopię istniejącej listy i wykonaj iterację nad nową.
for (String str : new ArrayList<String>(listOfStr))
{
listOfStr.remove(/* object reference or index */);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-26 05:28:35
Z tradycyjną pętlą for
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++;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-16 20:29:06
Ludzie twierdzą, że jeden nie może usunąć ze zbioru iterowanego przez pętlę foreach. Chciałem tylko zaznaczyć, że jest technicznie niepoprawne i dokładnie opisać (wiem, że pytanie OP jest na tyle zaawansowane, że o tym Nie wiem) kod leżący za tym założeniem:
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
}
}
Nie chodzi o to, że nie możesz usunąć z iteracji Colletion
, raczej o to, że nie możesz kontynuować iteracji, gdy to zrobisz. Stąd break
w powyższym kodzie.
Przepraszam, jeśli to 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 (mimo że ten wątek wydaje się bardziej niuansowy) tego i zablokowany.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-15 20:26:52
A ListIterator
umożliwia dodawanie lub usuwanie elementów z listy. Załóżmy, że masz listę Car
obiektów:
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);
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-13 15:16:20
Najlepszym sposobem (zalecanym) jest użycie Javy.util.Pakiet współbieżny . Przez korzystając z tego pakietu możesz łatwo uniknąć tego wyjątku . refer Zmodyfikowany Kod
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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-03 17:59:35
Mam sugestię na powyższy problem. Nie potrzeba dodatkowej listy ani dodatkowego czasu. Proszę znaleźć przykład, który zrobiłby to samo, ale w inny sposób.
//"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;
}
Pozwoli to uniknąć wyjątku dla współbieżności.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 18:41:25
W przypadku ArrayList: remove (int index) - jeśli (indeks jest pozycją ostatniego elementu) unika się bez System.arraycopy()
i nie zajmuje na to czasu.
Czas arraycopy wzrasta, jeśli (indeks maleje), przy okazji elementy listy również maleją!
Najlepszym skutecznym sposobem usuwania jest-usuwanie jego elementów w kolejności malejącej:
while(list.size()>0)list.remove(list.size()-1);
//takes O (1)
while(list.size()>0)list.remove(0);
//bierze O (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
- dla pętli indeksu: 1090 msec
- dla indeksu desc: 519 msec - - - the best
- dla iteratora: 1043 msec
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-04 17:25:28
ConcurrentHashMap lub ConcurrentLinkedQueuelub ConcurrentSkipListMap mogą być inną opcją, ponieważ nigdy nie wyrzucą one żadnego ConcurrentModificationException, nawet jeśli usuniesz lub dodasz element.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-23 11:18:58
for (Integer i : l)
{
if (i.intValue() == 5){
itemsToRemove.add(i);
break;
}
}
Haczyk jest po usunięciu elementu z listy, jeśli pominiesz wewnętrzny iterator.next () call. to nadal działa! Chociaż nie proponuję pisać kodu w ten sposób, pomaga to zrozumieć koncepcję stojącą za nim: -)
Zdrówko!Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 07:30:34
Przykład modyfikacji zbioru wątków:
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();
}
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-17 09:03:02
List<String> strings=new ArrayList<String>(){};
while(strings.size() > 0) {
String str = strings.remove(0);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-01 05:29:20
Możesz iterować listę używając for-loop i musisz wywołać list.Usuń (0) Musisz twardo zakodować indeks, parametr Usuń Indeks z zero. Zobacz też Ta odpowiedź :
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);
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-15 16:02:37
Collection<Integer> l = new ArrayList<Integer>();//Do the collection thing...
l.removeIf(i -> i == 5); //iterates through the collection and removes every occurence of 5
Wyrażenia Lambda i metody kolekcji w Jdk 8 przydają się i dodają trochę cukru składniowego .
Metoda removeIf
przeplata zbiór i filtruje za pomocą predykatu. Predykat jest funkcją argumentu zwracającego wartość logiczną...
Tak jak boolean _bool = (str) -> str.equals("text");
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-19 15:11:05
Wiem, że to pytanie jest za Stare, aby być o Javie 8, ale dla tych, którzy używają Javy 8, możesz łatwo użyć removeIf ():
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);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-26 20:15:21
To może nie jest najlepszy sposób, ale w większości małych przypadków powinien być akceptowalny:
"Utwórz drugą pustą tablicę i dodaj tylko te, które chcesz zachować"
nie pamiętam skąd to czytałem... dla justiness zrobię tę wiki w nadziei, że ktoś ją znajdzie lub po prostu nie zasłużyć na reputację, na którą nie zasługuję.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-12 04:57:52