Cara menghindari java.util.ConcurrentModificationException saat melakukan iterasi dan menghapus elemen dari ArrayList


203

Saya memiliki ArrayList yang ingin saya ulangi. Sementara iterasi di atasnya saya harus menghapus elemen pada saat bersamaan. Jelas ini melempar a java.util.ConcurrentModificationException.

Apa praktik terbaik untuk menangani masalah ini? Haruskah saya mengkloning daftar terlebih dahulu?

Saya menghapus elemen tidak di dalam loop itu sendiri tetapi bagian lain dari kode.

Kode saya terlihat seperti ini:

public class Test() {
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff() {
        for (A a : abc) 
        a.doSomething();
    }

    public void removeA(A a) {
        abc.remove(a);
    }
}

a.doSomethingmungkin memanggil Test.removeA();


Jawaban:


325

Dua pilihan:

  • Buat daftar nilai yang ingin Anda hapus, tambahkan ke daftar itu dalam loop, lalu panggil originalList.removeAll(valuesToRemove)di akhir
  • Gunakan remove()metode pada iterator itu sendiri. Perhatikan bahwa ini berarti Anda tidak dapat menggunakan loop yang disempurnakan untuk.

Sebagai contoh dari opsi kedua, menghapus string apa pun dengan panjang lebih dari 5 dari daftar:

List<String> list = new ArrayList<String>();
...
for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {
    String value = iterator.next();
    if (value.length() > 5) {
        iterator.remove();
    }
}

2
Saya seharusnya menyebutkan bahwa saya menghapus elemen di bagian lain dari kode dan bukan loop itu sendiri.
RoflcoptrException

@Roflcoptr: Yah sulit untuk menjawab tanpa melihat bagaimana kedua bit kode berinteraksi. Pada dasarnya, Anda tidak bisa melakukan itu. Tidak jelas apakah mengkloning daftar terlebih dahulu akan membantu, tanpa melihat bagaimana semuanya tergantung bersama. Bisakah Anda memberikan rincian lebih lanjut dalam pertanyaan Anda?
Jon Skeet

Saya tahu bahwa mengkloning daftar akan membantu, tetapi saya tidak tahu apakah itu pendekatan yang baik. Tetapi saya akan menambahkan beberapa kode lagi.
RoflcoptrException

2
Solusi ini juga mengarah ke java.util.ConcurrentModificationException, lihat stackoverflow.com/a/18448699/2914140 .
CoolMind

1
@ CoolMind: Tanpa banyak utas, kode ini harus baik-baik saja.
Jon Skeet

17

Dari JavaDocs di ArrayList

Iterator yang dikembalikan oleh iterator dan metode listIterator kelas ini gagal-cepat: jika daftar diubah secara struktural setiap saat setelah iterator dibuat, dengan cara apa pun kecuali melalui iterator sendiri yang menghapus atau menambahkan metode , iterator akan membuang ConcurrentModificationException.


6
dan di mana jawaban untuk pertanyaan itu?
Adelin

Seperti katanya, kecuali melalui iterator sendiri menghapus atau menambahkan metode
Varun Achar

14

Anda mencoba untuk menghapus nilai dari daftar dalam lanjutan "untuk loop", yang tidak mungkin, bahkan jika Anda menerapkan trik apa pun (yang Anda lakukan dalam kode Anda). Cara yang lebih baik adalah dengan kode tingkat iterator seperti yang disarankan di sini.

Saya bertanya-tanya bagaimana orang belum menyarankan pendekatan loop tradisional.

for( int i = 0; i < lStringList.size(); i++ )
{
    String lValue = lStringList.get( i );
    if(lValue.equals("_Not_Required"))
    {
         lStringList.remove(lValue);
         i--; 
    }  
}

Ini juga berfungsi.


2
Ini tidak benar !!! ketika Anda menghapus elemen, yang berikutnya mengambil posisi dan sementara saya meningkatkan elemen berikutnya tidak dicentang di iterasi berikutnya. Dalam hal ini Anda harus menggunakan (int i = lStringList.size (); i> -1; i--)
Johntor

1
Setuju! Alternatifnya adalah melakukan i--; jika kondisi dalam untuk loop.
suhas0sn07

Saya pikir jawaban ini telah diedit untuk mengatasi masalah dalam komentar di atas, sehingga seperti sekarang berfungsi dengan baik, setidaknya untuk saya.
Kira Resari

11

Anda harus benar-benar hanya mengulangi kembali array dengan cara tradisional

Setiap kali Anda menghapus elemen dari daftar, elemen sesudahnya akan didorong maju. Selama Anda tidak mengubah elemen selain yang iterasi, kode berikut ini akan berfungsi.

public class Test(){
    private ArrayList<A> abc = new ArrayList<A>();

    public void doStuff(){
        for(int i = (abc.size() - 1); i >= 0; i--) 
            abc.get(i).doSomething();
    }

    public void removeA(A a){
        abc.remove(a);
    }
}

10

Di Java 8 Anda bisa menggunakan Collection Interface dan melakukan ini dengan memanggil metode removeIf:

yourList.removeIf((A a) -> a.value == 2);

Informasi lebih lanjut dapat ditemukan di sini


6

Lakukan perulangan dengan cara biasa, java.util.ConcurrentModificationExceptionyaitu kesalahan yang berhubungan dengan elemen yang diakses.

Jadi cobalah:

for(int i = 0; i < list.size(); i++){
    lista.get(i).action();
}

Anda menghindari java.util.ConcurrentModificationExceptiondengan tidak menghapus apa pun dari daftar. Rumit. :) Anda tidak dapat benar-benar menyebut ini "cara biasa" untuk mengulang daftar.
Zsolt Sky

6

Saat iterasi daftar, jika Anda ingin menghapus elemen dimungkinkan. Coba lihat di bawah ini contoh saya,

ArrayList<String>  names = new ArrayList<String>();
        names.add("abc");
        names.add("def");
        names.add("ghi");
        names.add("xyz");

Saya memiliki nama-nama daftar Array di atas. Dan saya ingin menghapus nama "def" dari daftar di atas,

for(String name : names){
    if(name.equals("def")){
        names.remove("def");
    }
}

Kode di atas melempar pengecualian ConcurrentModificationException karena Anda memodifikasi daftar saat iterasi.

Jadi, untuk menghapus nama "def" dari Arraylist dengan melakukan cara ini,

Iterator<String> itr = names.iterator();            
while(itr.hasNext()){
    String name = itr.next();
    if(name.equals("def")){
        itr.remove();
    }
}

Kode di atas, melalui iterator kita dapat menghapus "def" nama dari Arraylist dan mencoba untuk mencetak array, Anda akan melihat output di bawah ini.

Output: [abc, ghi, xyz]


Selain itu, kita dapat menggunakan daftar bersamaan yang tersedia dalam paket bersamaan, sehingga Anda dapat melakukan hapus dan tambahkan operasi saat iterasi. Misalnya, lihat cuplikan kode di bawah ini. ArrayList <String> names = new ArrayList <String> (); CopyOnWriteArrayList <String> copyNames = new CopyOnWriteArrayList <String> (nama); untuk (String name: copyNames) {if (name.equals ("def")) {copyNames.remove ("def"); }}
Indra K

CopyOnWriteArrayList akan menjadi operasi yang paling mahal.
Indra K

5

Salah satu opsi adalah memodifikasi removeAmetode ini -

public void removeA(A a,Iterator<A> iterator) {
     iterator.remove(a);
     }

Tetapi ini berarti Anda doSomething()harus dapat lulus iteratorke removemetode. Bukan ide yang sangat bagus.

Dapatkah Anda melakukan ini dalam pendekatan dua langkah: Pada loop pertama ketika Anda mengulangi daftar, alih-alih menghapus elemen yang dipilih, tandai mereka sebagai yang akan dihapus . Untuk ini, Anda cukup menyalin elemen-elemen ini (salinan dangkal) ke yang lain List.

Kemudian, setelah iterasi Anda selesai, cukup lakukan removeAlldari daftar pertama semua elemen dalam daftar kedua.


Luar biasa, saya menggunakan pendekatan yang sama, meskipun saya mengulang dua kali. itu membuat hal-hal sederhana dan tidak ada masalah bersamaan dengan itu :)
Pankaj Nimgade

1
Saya tidak melihat bahwa Iterator memiliki metode hapus (a). Penghapusan () tidak memerlukan argumen docs.oracle.com/javase/8/docs/api/java/util/Iterator.html apa yang saya lewatkan?
c0der

5

Berikut adalah contoh di mana saya menggunakan daftar yang berbeda untuk menambahkan objek untuk dihapus, kemudian setelah itu saya menggunakan stream.foreach untuk menghapus elemen dari daftar asli:

private ObservableList<CustomerTableEntry> customersTableViewItems = FXCollections.observableArrayList();
...
private void removeOutdatedRowsElementsFromCustomerView()
{
    ObjectProperty<TimeStamp> currentTimestamp = new SimpleObjectProperty<>(TimeStamp.getCurrentTime());
    long diff;
    long diffSeconds;
    List<Object> objectsToRemove = new ArrayList<>();
    for(CustomerTableEntry item: customersTableViewItems) {
        diff = currentTimestamp.getValue().getTime() - item.timestamp.getValue().getTime();
        diffSeconds = diff / 1000 % 60;
        if(diffSeconds > 10) {
            // Element has been idle for too long, meaning no communication, hence remove it
            System.out.printf("- Idle element [%s] - will be removed\n", item.getUserName());
            objectsToRemove.add(item);
        }
    }
    objectsToRemove.stream().forEach(o -> customersTableViewItems.remove(o));
}

Saya pikir Anda sedang melakukan pekerjaan ekstra dengan menjalankan dua loop, dalam kasus terburuk loop akan menjadi seluruh daftar. Akan menjadi paling sederhana dan lebih murah melakukannya hanya dalam satu putaran.
Luis Carlos

Saya tidak berpikir Anda dapat menghapus objek dari dalam loop pertama, maka kebutuhan untuk loop penghapusan tambahan, juga loop penghapusan hanya objek untuk dihapus - mungkin Anda bisa menulis contoh hanya dengan satu loop, saya ingin melihatnya - terima kasih @ LuisCarlos
serup

Seperti yang Anda katakan dengan kode ini, Anda tidak dapat menghapus elemen di dalam for-loop karena menyebabkan pengecualian java.util.ConcurrentModificationException. Namun Anda dapat menggunakan dasar untuk. Di sini saya menulis contoh menggunakan bagian dari kode Anda.
Luis Carlos

1
for (int i = 0; i <customersTableViewItems.size (); i ++) {diff = currentTimestamp.getValue (). getTime () - customersTableViewItems.get (i) .timestamp.getValue (). getTime (); diffSeconds = diff / 1000% 60; if (diffSeconds> 10) {customersTableViewItems.remove (i--); }} Penting saya-- karena Anda tidak ingin melewatkan elemen apa pun. Anda juga dapat menggunakan metode removeIf (Predicate <? Super E> filter) yang disediakan oleh kelas ArrayList. Semoga bantuan ini
Luis Carlos

1
Pengecualian terjadi karena dalam for-loop ada sebagai referensi aktif untuk iterator dari daftar. Dalam normal untuk, tidak ada referensi dan Anda memiliki lebih banyak fleksibilitas untuk mengubah data. Semoga bantuan ini
Luis Carlos

3

Alih-alih menggunakan Untuk setiap loop, gunakan normal untuk loop. misalnya, kode di bawah ini menghapus semua elemen dalam daftar array tanpa memberikan java.util.ConcurrentModificationException. Anda dapat memodifikasi kondisi dalam loop sesuai dengan kasus penggunaan Anda.

   for(int i=0;i<abc.size();i++)  {

          e.remove(i);
        }

2

Lakukan beberapa cara sederhana seperti ini:

for (Object object: (ArrayList<String>) list.clone()) {
    list.remove(object);
}

2

Solusi Java 8 alternatif menggunakan stream:

        theList = theList.stream()
            .filter(element -> !shouldBeRemoved(element))
            .collect(Collectors.toList());

Di Java 7 Anda bisa menggunakan Jambu biji sebagai gantinya:

        theList = FluentIterable.from(theList)
            .filter(new Predicate<String>() {
                @Override
                public boolean apply(String element) {
                    return !shouldBeRemoved(element);
                }
            })
            .toImmutableList();

Perhatikan, bahwa contoh Guava menghasilkan daftar yang tidak berubah yang mungkin atau mungkin tidak seperti yang Anda inginkan.


1

Anda juga dapat menggunakan CopyOnWriteArrayList sebagai ganti ArrayList. Ini adalah pendekatan yang direkomendasikan terbaru oleh dari JDK 1.5 dan seterusnya.


1

Dalam kasus saya, jawaban yang diterima tidak berfungsi, ini menghentikan Pengecualian tetapi menyebabkan beberapa inkonsistensi dalam Daftar saya. Solusi berikut ini sangat cocok untuk saya.

List<String> list = new ArrayList<>();
List<String> itemsToRemove = new ArrayList<>();

for (String value: list) {
   if (value.length() > 5) { // your condition
       itemsToRemove.add(value);
   }
}
list.removeAll(itemsToRemove);

Dalam kode ini, saya telah menambahkan item untuk dihapus, dalam daftar lain dan kemudian menggunakan list.removeAllmetode untuk menghapus semua item yang diperlukan.


0

"Haruskah aku mengkloning daftar itu dulu?"

Itu akan menjadi solusi termudah, hapus dari klon, dan salin kembali klon setelah dihapus.

Contoh dari game rummikub saya:

SuppressWarnings("unchecked")
public void removeStones() {
  ArrayList<Stone> clone = (ArrayList<Stone>) stones.clone();
  // remove the stones moved to the table
  for (Stone stone : stones) {
      if (stone.isOnTable()) {
         clone.remove(stone);
      }
  }
  stones = (ArrayList<Stone>) clone.clone();
  sortStones();
}

2
Downvoters setidaknya harus memberikan komentar sebelum downvoting.
OneWorld

2
Tidak ada yang salah secara inheren dengan jawaban ini, mungkin itu stones = (...) clone.clone();berlebihan. Tidak akan stones = clone;melakukan hal yang sama?
vikingsteve

Saya setuju, kloning kedua tidak perlu. Anda dapat menyederhanakan ini dengan mengulangi klon dan menghapus elemen langsung dari stones. Dengan cara ini Anda bahkan tidak perlu clonevariabel: for (Stone stone : (ArrayList<Stone>) stones.clone()) {...
Zsolt Sky

0

Jika tujuan Anda adalah untuk menghapus semua elemen dari daftar, Anda dapat mengulangi setiap item, dan kemudian menelepon:

list.clear()

0

Saya datang terlambat, saya tahu tetapi saya menjawab ini karena saya pikir solusi ini sederhana dan elegan:

List<String> listFixed = new ArrayList<String>();
List<String> dynamicList = new ArrayList<String>();

public void fillingList() {
    listFixed.add("Andrea");
    listFixed.add("Susana");
    listFixed.add("Oscar");
    listFixed.add("Valeria");
    listFixed.add("Kathy");
    listFixed.add("Laura");
    listFixed.add("Ana");
    listFixed.add("Becker");
    listFixed.add("Abraham");
    dynamicList.addAll(listFixed);
}

public void updatingListFixed() {
    for (String newList : dynamicList) {
        if (!listFixed.contains(newList)) {
            listFixed.add(newList);
        }
    }

    //this is for add elements if you want eraser also 

    String removeRegister="";
    for (String fixedList : listFixed) {
        if (!dynamicList.contains(fixedList)) {
            removeResgister = fixedList;
        }
    }
    fixedList.remove(removeRegister);
}

Semua ini untuk memperbarui dari satu daftar ke yang lain dan Anda dapat membuat semuanya hanya dari satu daftar dan dalam metode memperbarui Anda memeriksa kedua daftar dan dapat menghapus atau menambahkan elemen di antara daftar. Ini berarti kedua daftar selalu berukuran sama


0

Gunakan Iterator sebagai ganti Array List

Apakah set dikonversi ke iterator dengan jenis yang cocok

Dan pindah ke elemen selanjutnya dan hapus

Iterator<Insured> itr = insuredSet.iterator();
while (itr.hasNext()) { 
    itr.next();
    itr.remove();
}

Pindah ke yang berikutnya penting di sini karena harus mengambil indeks untuk menghapus elemen.


0

Bagaimana dengan

import java.util.Collections;

List<A> abc = Collections.synchronizedList(new ArrayList<>());

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.