Izbjegavanje ConcurrentModificationException u Javi

1. Uvod

U ovom ćemo članku pogledati ConcurrentModificationException razred.

Prvo ćemo dati objašnjenje kako to funkcionira, a zatim to dokazati pomoću testa za njegovo pokretanje.

Na kraju ćemo isprobati nekoliko zaobilaznih rješenja koristeći se praktičnim primjerima.

2. Okidanje a ConcurrentModificationException

U osnovi, ConcurrentModificationException je navikao na neuspješno kada se nešto što ponavljamo modificira. Dokažimo to jednostavnim testom:

@Test (očekuje se = ConcurrentModificationException.class) javna praznina dok RemovingDuringIteration_shouldThrowException () baca InterruptedException {Popis cjelobrojnih = newArrayList (1, 2, 3); for (Integer integer: integers) {integers.remove (1); }}

Kao što vidimo, prije završetka naše iteracije uklanjamo element. To je ono što pokreće iznimku.

3. Rješenja

Ponekad bismo zapravo htjeli ukloniti elemente iz zbirke tijekom iteracije. Ako je to slučaj, tada postoje neka rješenja.

3.1. Izravna upotreba iteratora

A za svakoga petlja koristi Iterator iza kulisa, ali je manje opširan. Međutim, ako smo refaktorirali naš prethodni test da bismo koristili Iterator, imat ćemo pristup dodatnim metodama, kao što su ukloniti(). Pokušajmo umjesto ove metode izmijeniti naš popis:

for (Iterator iterator = integers.iterator (); iterator.hasNext ();) {Integer integer = iterator.next (); if (cijeli broj == 2) {iterator.remove (); }}

Sad ćemo primijetiti da nema iznimke. Razlog tome je što ukloniti() metoda ne uzrokuje a ConcurrentModificationException. Sigurno je nazvati tijekom ponavljanja.

3.2. Ne uklanja se tijekom iteracije

Ako želimo zadržati svoje za svakoga petlja, onda možemo. Samo moramo pričekati nakon ponavljanja prije nego što uklonimo elemente. Isprobajmo ovo dodavanjem onoga što želimo ukloniti u ukloniti navesti dok ponavljamo:

Popis cijelih brojeva = newArrayList (1, 2, 3); Popis toRemove = newArrayList (); za (Integer integer: integers) {if (integer == 2) {toRemove.add (integer); }} integers.removeAll (toRemove); assertThat (integers) .containsExactly (1, 3); 

Ovo je još jedan učinkovit način zaobilaženja problema.

3.3. Koristeći removeIf ()

Java 8 je predstavila removeIf () metoda za Kolekcija sučelje. To znači da ako radimo s tim, možemo koristiti ideje funkcionalnog programiranja kako bismo ponovno postigli iste rezultate:

Popis cijelih brojeva = newArrayList (1, 2, 3); cijeli brojevi.removeIf (i -> i == 2); assertThat (integers) .containsExactly (1, 3);

Ovaj deklarativni stil nudi nam najmanje riječi. Međutim, ovisno o slučaju upotrebe, možda ćemo naći druge metode prikladnije.

3.4. Filtriranje pomoću streamova

Kada zaranjamo u svijet funkcionalnog / deklarativnog programiranja, možemo zaboraviti na mutiranje zbirki, umjesto toga, možemo se usredotočiti na elemente koje bi zapravo trebalo obraditi:

Cijeli brojevi zbirke = newArrayList (1, 2, 3); Popis prikupljenih = cijeli brojevi .stream () .filter (i -> i! = 2) .map (Object :: toString) .collect (toList ()); assertThat (prikupljeno) .containEhactly ("1", "3");

Učinili smo suprotno od našeg prethodnog primjera, pružajući predikat za određivanje elemenata koji uključuju, a ne izuzimaju. Prednost je što uz uklanjanje možemo povezati i druge funkcije. U primjeru koristimo funkciju karta(), ali mogli bismo upotrijebiti još više operacija ako to želimo.

4. Zaključak

U ovom smo članku prikazali probleme na koje biste mogli naići ako uklanjate predmete iz zbirke tijekom iteracije, a također smo pružili neka rješenja za negiranje problema.

Implementacija ovih primjera može se naći na GitHubu. Ovo je Maven projekt, pa bi ga trebalo biti lako pokrenuti kakav jest.