Menghindari ConcurrentModificationException di Java

1. Perkenalan

Pada artikel ini, kita akan melihat kelas ConcurrentModificationException .

Pertama, kami akan memberikan penjelasan cara kerjanya, dan kemudian membuktikannya dengan menggunakan tes untuk memicunya.

Terakhir, kami akan mencoba beberapa solusi dengan menggunakan contoh praktis.

2. Memicu ConcurrentModificationException

Pada dasarnya, ConcurrentModificationException digunakan untuk mempercepat kegagalan saat sesuatu yang kita iterasi diubah. Mari kita buktikan dengan tes sederhana:

@Test(expected = ConcurrentModificationException.class) public void whilstRemovingDuringIteration_shouldThrowException() throws InterruptedException { List integers = newArrayList(1, 2, 3); for (Integer integer : integers) { integers.remove(1); } }

Seperti yang bisa kita lihat, sebelum menyelesaikan iterasi kita menghapus sebuah elemen. Itulah yang memicu pengecualian.

3. Solusi

Terkadang, kami mungkin benar-benar ingin menghapus elemen dari koleksi saat melakukan iterasi. Jika ini masalahnya, maka ada beberapa solusi.

3.1. Menggunakan Iterator Secara Langsung

A for-each loop menggunakan Iterator di belakang layar tetapi tidak terlalu bertele-tele. Namun, jika kami memfaktorkan ulang pengujian sebelumnya untuk menggunakan Iterator, kami akan memiliki akses ke metode tambahan, seperti remove (). Mari coba gunakan metode ini untuk mengubah daftar kita:

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

Sekarang kita akan melihat bahwa tidak ada pengecualian. Alasannya adalah karena metode remove () tidak menyebabkan ConcurrentModificationException. Aman untuk menelepon saat melakukan iterasi.

3.2. Tidak Menghapus Selama Iterasi

Jika kita ingin mempertahankan for-each loop kita, maka kita bisa. Hanya saja kita perlu menunggu sampai setelah iterasi sebelum kita menghapus elemennya. Mari kita coba ini dengan menambahkan apa yang ingin kita hapus ke daftar toRemove saat kita mengulang:

List integers = newArrayList(1, 2, 3); List toRemove = newArrayList(); for (Integer integer : integers) { if(integer == 2) { toRemove.add(integer); } } integers.removeAll(toRemove); assertThat(integers).containsExactly(1, 3); 

Ini adalah cara efektif lain untuk mengatasi masalah.

3.3. Menggunakan removeIf ()

Java 8 memperkenalkan metode removeIf () ke antarmuka Collection . Ini berarti bahwa jika kita bekerja dengannya, kita dapat menggunakan gagasan pemrograman fungsional untuk mencapai hasil yang sama lagi:

List integers = newArrayList(1, 2, 3); integers.removeIf(i -> i == 2); assertThat(integers).containsExactly(1, 3);

Gaya deklaratif ini menawarkan kepada kita sedikit verbositas. Namun, bergantung pada kasus penggunaan, kami mungkin menemukan metode lain yang lebih nyaman.

3.4. Memfilter Menggunakan Aliran

Saat terjun ke dunia pemrograman fungsional / deklaratif, kita bisa melupakan mutasi koleksi, sebagai gantinya, kita bisa fokus pada elemen yang seharusnya diproses:

Collection integers = newArrayList(1, 2, 3); List collected = integers .stream() .filter(i -> i != 2) .map(Object::toString) .collect(toList()); assertThat(collected).containsExactly("1", "3");

Kita telah melakukan kebalikan dari contoh kita sebelumnya, dengan memberikan predikat untuk menentukan elemen yang akan disertakan, bukan dikecualikan. Keuntungannya adalah kita dapat menyatukan fungsi-fungsi lain bersamaan dengan penghapusan. Dalam contoh, kita menggunakan peta fungsional (), tetapi bisa menggunakan lebih banyak operasi jika kita mau.

4. Kesimpulan

Dalam artikel ini kami telah menunjukkan masalah yang mungkin Anda temui jika Anda menghapus item dari koleksi saat melakukan iterasi, dan juga memberikan beberapa solusi untuk meniadakan masalah tersebut.

Penerapan contoh-contoh ini dapat ditemukan di GitHub. Ini adalah proyek Maven, jadi harus mudah dijalankan apa adanya.