Iterate per kolekciją, išvengiant ConcurrentModificationException išimties, kai ištrinate objektus kilpoje

Visi žinome, kad negalite to padaryti:

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

ConcurrentModificationException etc ... tai, atrodo, kartais veikia, bet ne visada. Štai keletas konkrečių kodų:

22 окт. Claudiu yra nustatytas spalio 22 d 2008-10-22 02:23 '08 at 2:23 am 2008-10-22 02:23
@ 25 atsakymai

Iterator.remove() yra saugus, galite jį naudoti taip:

 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(); } } 

Atkreipkite dėmesį, kad „ Iterator.remove() yra vienintelis saugus būdas keisti kolekciją iteracijos metu; Elgesys yra neapibrėžtas, jei iteracijos metu pagrindinė kolekcija yra kitaip pakeista.

Šaltinis: docs.oracle> Kolekcijos sąsaja


Taip pat, jei turite „ ListIterator ir norite pridėti elementų, galite naudoti „ ListIterator#add , dėl tos pačios priežasties, kad galite naudoti „ Iterator#remove - tai skirta šiam tikslui.


Jūsų atveju bandėte pašalinti iš sąrašo, tačiau tie patys apribojimai taikomi, jei bandote put į Map kai ieškote jo turinio.

1501
22 окт. Bill K Oct 22 atsakymas 2008-10-22 02:27 '08, 02:27, 2008-10-22 02:27

Jis veikia:

 Iterator<Integer> iter = l.iterator(); while (iter.hasNext()) { if (iter.next().intValue() == 5) { iter.remove(); } } 
border=0

Dariau prielaidą, kad nuo foreach ciklo yra sintaksinis cukrus iteracijai, o iteratoriaus naudojimas nepadės ... bet tai suteikia jums .remove() .

327
22 окт. Claudiu atsakė spalio 22 d 2008-10-22 02:26 '08, 02:26, 2008-10-22 02:26

Naudodami „Java 8“ galite naudoti naują „ removeIf metodą . Taikomas jūsų pavyzdžiui:

 Collection<Integer> coll = new ArrayList<Integer>(); //populate coll.removeIf(i -> i.intValue() == 5); 
169
28 мая '14 в 13:11 2014-05-28 13:11 atsakymas pateikiamas asylias apie gegužės 28 d. 14 d. 13:11 2014-05-28 13:11

Kadangi jau buvo atsakyta į klausimą, geriausias būdas yra naudoti iteratoriaus objekto ištrynimo būdą, norėčiau pereiti prie vietos, kurioje išduodama klaida "java.util.ConcurrentModificationException" savybės.

Kiekvienoje kolekcijos klasėje yra privati ​​klasė, kuri įgyvendina „Iterator“ sąsają ir pateikia tokius metodus kaip next() , remove() ir hasNext() .

Šis kodas atrodo taip:

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

Čia checkForComodification metodo checkForComodification metodas įgyvendinamas kaip

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

Taigi, kaip matote, jei aiškiai bandote pašalinti elementą iš kolekcijos. Dėl šios priežasties modCount skiriasi nuo expectedModCount modCount , o tai reiškia „ ConcurrentModificationException .

40
15 мая '10 в 22:57 2010-05-15 22:57 atsakymą pateikė Ashish , gegužės 15 d. 10 val. 22:57 2010-05-15 22:57

Galite naudoti iteratorių tiesiogiai, kaip minėjote, arba išsaugoti antrą rinkinį ir pridėti kiekvieną elementą, kurį norite ištrinti, į naują kolekciją, o tada ištrinti viską. Tai leidžia naudoti kiekvieno tipo ciklų saugos tipą, didinant atminties naudojimą ir procesoriaus laiką (tai neturėtų būti didžiulė problema, jei neturite tikrai didelių sąrašų ar tikrai seno kompiuterio)

 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); } 
23
22 окт. Atsakymą pateikė RodeoClown spalio 22 d. 2008-10-22 02:32 '08, 2:32 2008-10-22 02:32

Tokiais atvejais bendras triukas (buvo?) Grįžti atgal:

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

Tačiau aš esu daugiau nei laimingas, kad, pavyzdžiui, „Java 8“ turite geresnių būdų. removeIf arba filter srautuose.

17
29 авг. atsakymą pateikė Landei 29 rug . 2014-08-29 12:56 '14, 12:56 2014-08-29 12:56

Tas pats atsakymas kaip ir Claudius su kilpa:

 for (Iterator<Object> it = objects.iterator(); it.hasNext();) { Object object = it.next(); if (test) { it.remove(); } } 
16
21 авг. Atsakymas duotas Antzi 21 rug . 2013-08-21 15:39 '13, 15:39, 2013-08-21 15:39

Iš „ Eclipse“ kolekcijos (anksčiau GS kolekcijos ) „ removeIf apibrėžtas „ removeIf metodas veiks:

 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); 

Naudojant „Java 8 Lambda“ sintaksę, tai galima parašyti taip:

 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); 

Čia reikalingas skambutis į „ Predicates.cast() nes „Java 8“ buvo pridėtas numatytasis „ removeIf metodas prie java.util.Collection sąsajos.

Pastaba Aš esu „ Eclipse“ kolekcijos atstovas .

11
19 дек. Donaldas Raabas atsakė gruodžio 19 d. 2012-12-19 02:08 '12, 02:08 2012-12-19 02:08

Padarykite egzistuojančio sąrašo kopiją ir eikite į naują kopiją.

 for (String str : new ArrayList<String>(listOfStr)) { listOfStr.remove(); } 
7
26 июня '12 в 8:28 2012-06-26 08:28 atsakymą pristato „ Priyank Doshi “ birželio 26 d. 12 val. 8:28 2012-06-26 08:28

Su tradiciniu ciklu

 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++; } 
7
16 апр. Atsakyti Lluis Felisart Bal 2017-04-16 23:29 '17, 11:29, 2017-04-16 23:29

Žmonės teigia, kad negalima pašalinti iš kolekcijos, kuri kartojasi foreach kilpoje. Aš tik norėjau atkreipti dėmesį į tai, kad techniškai neteisinga ir tiksliai apibūdinama (žinau, kad OP klausimas yra taip išplėstas, kad jo žinios nebūtų įtrauktos).

  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 } } 

Tai nereiškia, kad negalite ištrinti iš Colletion , bet ne tęsti, tada tęsti. Todėl break kodą.

Jis atsiprašo, jei šis atsakymas yra šiek tiek specializuotas precedentas ir labiau tinka pradinei temai, iš kurios aš atėjau, šis ženklas pažymėtas kaip dublikatas (nepaisant šio srauto, pasirodančio niuansingiau) ir užblokuotas.

6
17 марта '18 в 14:02 2018-03-17 14:02 Atsakymą pateikė Jonas kovo 17 d. 18 val. 2:02 val. 2018-03-17 14:02

ListIterator leidžia pridėti arba pašalinti elementus sąraše. Tarkime, kad turite Car objektų sąrašą:

 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); } } 
2
13 окт. atsakymas duotas james.garriss 13 spalis 2017-10-13 18:16 '17, 18:16 pm 2017-10-13 18:16

„ConcurrentHashMap“ arba „ ConcurrentLinkedQueue“ arba „ ConcurrentSkipListMap“ gali būti dar viena parinktis, nes jie niekada neišmeta jokio ConcurrentModificationException, net jei pašalinsite arba pridėsite elementą.

1
23 июня '16 в 14:18 2016-06-23 14:18 atsakymą pateikė „ yessybirželio 16–16 d . 14:18 2016-06-23 14:18

Turiu pasiūlymą dėl šios problemos. Nereikia papildomo sąrašo ar papildomo laiko. Raskite pavyzdį, kuris padarys tą patį, bet kitaip.

 //"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; } 

Taip išvengiama išimties, lyginant su sutapimu.

1
19 нояб. Atsakymą pateikė Nandhan Thiravia, lapkričio 19 d 2013-11-19 12:18 '13, 12:18, 2013-11-19 12:18

Geriausias būdas (rekomenduojama) yra naudoti java.util.Concurrent paketą. Naudodami šį paketą galite lengvai išvengti šios išimties. žr. pakeistą 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); } 
1
03 мая '18 в 20:59 2018-05-03 20:59 atsakymą duodamas jagdish khetre 03 gegužės 18 d. 20:59 2018-05-03 20:59

ArrayList“: pašalinti (int indeksas) - jei (indeksas yra paskutinė elemento padėtis), jis vengia be „ System.arraycopy() ir tam nereikia laiko.

Mascopijos laikas didėja, jei (indeksas mažėja), beje, sąrašo elementai taip pat mažėja!

Geriausias efektyvus pašalinimo būdas yra ištrinti jo elementus mažėjančia tvarka: while(list.size()>0)list.remove(list.size()-1); // užima O (1), while(list.size()>0)list.remove(0); // priima O (faktorius (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 
  • už indekso ciklą: 1090 ms
  • desc indeksui: 519 msek --- geriausia
  • iteratoriui: 1043 ms
0
04 февр. Atsakymas pateiktas Nurlan 04 vasario mėn. 2016-02-04 20:25 '16 at 8:25 pm 2016-02-04 20:25

Žinau, kad šis klausimas yra per senas, kad būtų apie „Java 8“, bet tiems, kurie naudoja „Java 8“, galite lengvai naudoti „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); 
0
26 сент. atsakymas duodamas pedram bashiri 26 sep . 2018-09-26 23:15 '18 at 11:15 pm 2018-09-26 23:15

Saugaus rinkimo srauto pakeitimo pavyzdys:

 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(); } } } } 
0
17 сент. atsakymas pateikiamas „ Yazon2006“ 17 sep . 2018-09-17 12:03 '18, 12:03 val. 2018-09-17 12:03
 List<String> strings=new ArrayList<String>(){}; while(strings.size() > 0) { String str = strings.remove(0); } 
0
01 янв. atsakymas pateikiamas developer747 01 Jan. 2018-01-01 08:29 '18 at 8:29 am 2018-01-01 08:29

Žinau, kad šis klausimas yra tik Collection , o ne konkretus List . Tačiau tiems, kurie skaito šį klausimą ir faktiškai dirba su nuoroda į List , galite naudoti „ ConcurrentModificationException išimtį su kai kuriais laikais (kai keičiasi), jei norite išvengti „ Iterator (arba, jei nenorite, kad jis būtų visiškai arba konkrečiai venkite to, kad kiekviename elemente būtų pasiektas kitoks, nei kad galų gale, kuris būtų vienintelis būdas, kurį gali padaryti pats Iterator ):

* Atnaujinimas: žr.

 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; } } 

Iš šio kodo nėra „ConcurrentModificationException“.

Čia matome, kad ciklas neprasideda pradžioje ir nesibaigia kiekviename elemente (kuris, manau, pats Iterator negali).

FWIW, mes taip pat matome, get pakviesti į list , kuris negali būti atliktas, jei jo nuoroda buvo tik „ Collection (vietoj specifiškesnio „ Collection tipo List ) - List sąsaja apima get , bet „ Collection sąsaja nėra. Jei tai nebūtų šio skirtumo, tada nuoroda į list galėtų būti Collection [ir todėl techniškai šis atsakymas būtų tiesioginis atsakymas, o ne tangentinis atsakymas].

FWIWW tas pats kodas vis dar veikia po pakeitimo, kad pradėtų nuo kiekvieno elemento pradžios iki sustabdymo (kaip „ Iterator užsakymas):

 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; } } 
0
20 дек. atsakymas pateikiamas 20 d. 2018-12-20 02:40 '18, 2:40 2018-12-20 02:40
 for (Integer i : l) { if (i.intValue() == 5){ itemsToRemove.add(i); break; } } 

Spąstai yra elemento pašalinimas iš sąrašo, jei praleidote vidinį skambutį į iterator.next (). jis vis dar veikia! Nors aš neketinu parašyti tokio kodo, tai padeda suprasti, kas yra jos sąvoka: -)

Hooray!

0
30 июня '16 в 10:24 2016-06-30 10:24 atsakymą pateikė Srinivasan Thoyyeti, birželio 16, 16 d., 10:24 2016-06-30 10:24

Be „ @assylias atsakymo, galite naudoti naują „ Stream api“, jei naudojate „Java 8“:

 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); 

Jei apverčiate sąlygą, sprendimas tampa dar glaustesnis, nes jums nereikia atmesti negate() predikato, kuris leidžia tik naudoti metodo nuorodą:

 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); 

Vienas iš šio grožio yra tai, kad srautas yra įvertintas tingiai, t.y. filter() operacija faktiškai neįvertinama, kol ji nebus naudojama terminalo operacijoje, pvz., „ forEach() . Daugiau apie tai galite sužinoti „Oracle“ mokymo programoje .

-1
01 мая '15 в 23:43 2015-05-01 23:43 atsakymas duotas matsev 01 gegužės 15 d. 23:43 2015-05-01 23:43
 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 

Lambda išraiškos ir rinkimo metodai Jdk 8 yra įtraukti į „Handy“ ir pridedama sintaksinio cukraus cukraus.

removeIf metodas nuskaito rinkinį ir filtruoja jį su „Predicate“. Predikatas yra argumento, kuris grąžina loginę vertę, funkcija ... Tiesiog kaip boolean _bool = (str) → str.equals("text");

-2
19 июля '18 в 5:59 2018-07-19 05:59 Atsakymą pateikė Barnabas Ukwuani liepos 19 d., 18 val., 5:59 2018-07-19 05:59

Jūs galite kartoti per sąrašą naudodami for-loop, ir jums reikia skambinti list.remove (0). Norint pašalinti pašalinimo parametrą, reikia indeksuoti kietąjį kodą su nuliu. Taip pat žiūrėkite šį atsakymą :

 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); } 
-2
15 янв. atsakymą pateikė Ravi sausio 15 d 2018-01-15 17:55 '18 at 17:55 pm 2018-01-15 17:55

tai gali būti ne geriausias būdas, tačiau daugeliu atvejų tai turėtų būti priimtina:

„sukurkite antrą tuščią masyvą ir pridėkite tik tuos, kuriuos norite išsaugoti“

Aš neprisimenu, kur aš perskaičiau ... teisingumui, atliksiu šį wiki tikėdamasi, kad kažkas jį ras arba tiesiog neužsidirbs reputacijos, kurios nenusipelno.

-3
12 окт. atsakymą pateikė ajax333221 spalio 12 d. 2013-10-12 07:57 '13, 7:57, 2013-10-12 07:57

Kiti klausimai apie arba Užduoti klausimą