Pertanyaan Iterasi melalui Koleksi, hindari ConcurrentModificationException saat melepas loop


Kita semua tahu Anda tidak bisa melakukan ini:

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

ConcurrentModificationException dll ... ini tampaknya berfungsi kadang-kadang, tetapi tidak selalu. Berikut ini beberapa kode khusus:

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

Ini, tentu saja, menghasilkan:

Exception in thread "main" java.util.ConcurrentModificationException

... meskipun beberapa utas tidak melakukannya ... Pokoknya.

Apa solusi terbaik untuk masalah ini? Bagaimana cara menghapus item dari koleksi dalam satu lingkaran tanpa membuang pengecualian ini?

Saya juga menggunakan sewenang-wenang Collection di sini, belum tentu sebuah ArrayList, jadi Anda tidak bisa mengandalkan get.


1028
2017-10-21 23:23


asal


Jawaban:


Iterator.remove() aman, Anda dapat menggunakannya seperti ini:

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

Perhatikan itu Iterator.remove() adalah satu-satunya cara aman untuk memodifikasi koleksi selama iterasi; perilaku tidak ditentukan jika koleksi yang mendasari dimodifikasi dengan cara lain ketika iterasi sedang berlangsung.

Sumber: docs.oracle> The Collection Interface


Dan juga, jika Anda memiliki ListIterator dan mau menambahkan item yang bisa Anda gunakan ListIterator#add, untuk alasan yang sama yang dapat Anda gunakan Iterator#remove- Ini dirancang untuk memperbolehkannya.


1466
2017-10-21 23:27



Ini bekerja:

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

Saya berasumsi bahwa karena loop foreach adalah gula sintaksis untuk iterasi, menggunakan iterator tidak akan membantu ... tetapi memberi Anda ini .remove() fungsionalitas.


320
2017-10-21 23:26



Dengan Java 8 yang bisa Anda gunakan yang baru removeIf metode. Diterapkan pada contoh Anda:

Collection<Integer> coll = new ArrayList<Integer>();
//populate

coll.removeIf(i -> i.intValue() == 5);

156
2018-05-28 10:11



Karena pertanyaan sudah dijawab yaitu cara terbaik adalah menggunakan metode hapus objek iterator, saya akan membahas secara spesifik tempat di mana kesalahan "java.util.ConcurrentModificationException" dilemparkan.

Setiap kelas koleksi memiliki kelas privat yang mengimplementasikan antarmuka Iterator dan menyediakan metode seperti next(), remove() dan hasNext().

Kode untuk selanjutnya terlihat seperti ini ...

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

Di sini metode checkForComodification diimplementasikan sebagai

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

Jadi, seperti yang Anda lihat, jika Anda secara eksplisit mencoba menghapus elemen dari koleksi. Hasilnya modCount semakin berbeda dari expectedModCount, menghasilkan pengecualian ConcurrentModificationException.


38
2018-05-15 19:57



Anda dapat menggunakan iterator langsung seperti yang Anda sebutkan, atau menyimpan koleksi kedua dan menambahkan setiap item yang ingin Anda hapus ke koleksi baru, lalu hapus Semua di bagian akhir. Hal ini memungkinkan Anda untuk tetap menggunakan jenis-keamanan dari for-each loop dengan biaya peningkatan penggunaan memori dan waktu cpu (seharusnya tidak menjadi masalah besar kecuali Anda memiliki daftar yang benar-benar besar atau komputer yang benar-benar lama)

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

22
2017-10-21 23:32



Dalam kasus seperti itu trik umum adalah (apakah?) Untuk mundur:

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

Yang mengatakan, saya lebih dari senang bahwa Anda memiliki cara yang lebih baik di Java 8, mis. removeIf atau filter di aliran.


17
2017-08-29 09:56



Jawaban yang sama seperti Claudius dengan for loop:

for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
    Object object = it.next();
    if (test) {
        it.remove();
    }
}

14
2017-08-21 12:39



Dengan Koleksi Eclipse (dahulu Koleksi GS), metode removeIf didefinisikan pada MutableCollection akan bekerja:

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

Dengan sintaks Java 8 Lambda ini dapat ditulis sebagai berikut:

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

Panggilan ke Predicates.cast() diperlukan di sini karena default removeIf metode ditambahkan pada java.util.Collection antarmuka di Java 8.

catatan: Saya seorang komiter untuk Koleksi Eclipse.


11
2017-12-18 23:08



Buat salinan dari daftar yang ada dan ulangi pada salinan baru.

for (String str : new ArrayList<String>(listOfStr))     
{
    listOfStr.remove(/* object reference or index */);
}

6
2018-06-26 05:28