Mengapa saya tidak mendapatkan java.util.ConcurrentModificationException dalam contoh ini?


176

Catatan: Saya mengetahui Iterator#remove()metode ini.

Dalam contoh kode berikut, saya tidak mengerti mengapa metode List.removein mainmelempar ConcurrentModificationException, tetapi tidak dalam removemetode.

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer toRemove) {
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(toRemove)) {                
                integerList.remove(integer);
            }
        }
    }
}

3
Satu-satunya cara aman untuk menghapus elemen dari daftar saat iterasi daftar itu adalah dengan menggunakan Iterator#remove(). Mengapa kamu melakukannya dengan cara ini?
Matt Ball

@MattBall: Saya hanya mencoba melihat apa alasannya di sini. Karena, itu sama "ditingkatkan untuk loop" di kedua metode tetapi satu melempar ConcurrentModificationExceptiondan yang lainnya tidak.
Bhesh Gurung

Ada perbedaan dalam elemen yang Anda hapus. Dalam metode Anda menghapus 'elemen tengah'. Di utama Anda menghapus yang terakhir. Jika Anda menukar angka Anda mendapatkan pengecualian dalam metode Anda. Masih tidak yakin mengapa itu terjadi.
Ben van Gompel

Saya memiliki masalah yang sama, ketika loop saya mengulangi juga posisi yang tidak ada setelah saya menghapus item dalam loop. Saya cukup memperbaiki ini dengan menambahkan return;ke dalam loop.
frank17

di java8 Android, menghapus elemen selain yang terakhir akan memanggil ConcurrentModificationException. jadi untuk kasus Anda, fungsi hapus akan mendapatkan pengecualian yang berlawanan seperti yang Anda amati sebelumnya.
gonglong

Jawaban:


262

Inilah sebabnya: Seperti yang dikatakan dalam Javadoc:

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.

Pemeriksaan ini dilakukan dalam next()metode iterator (seperti yang Anda lihat dengan stacktrace). Tetapi kita akan mencapai next()metode hanya jika hasNext()disampaikan benar, yang disebut oleh masing-masing untuk memeriksa apakah batas terpenuhi. Dalam metode penghapusan Anda, ketika hasNext()memeriksa apakah perlu mengembalikan elemen lain, ia akan melihat bahwa ia mengembalikan dua elemen, dan sekarang setelah satu elemen dihapus daftar hanya berisi dua elemen. Jadi semuanya sangat bagus dan kita selesai dengan iterasi. Pemeriksaan untuk modifikasi bersamaan tidak terjadi, karena hal ini dilakukan dalam next()metode yang tidak pernah disebut.

Selanjutnya kita sampai pada loop kedua. Setelah kami menghapus angka kedua metode hasNext akan memeriksa lagi jika dapat mengembalikan lebih banyak nilai. Sudah mengembalikan dua nilai, tetapi daftar sekarang hanya berisi satu. Tetapi kode di sini adalah:

public boolean hasNext() {
        return cursor != size();
}

1! = 2, jadi kami melanjutkan ke next()metode, yang sekarang menyadari bahwa seseorang telah mengacaukan daftar dan mengeluarkan pengecualian.

Semoga itu membersihkan pertanyaan Anda.

Ringkasan

List.remove()tidak akan membuang ConcurrentModificationExceptionketika menghapus elemen terakhir kedua dari daftar.


5
@pushy: Hanya jawaban yang tampaknya menjawab apa yang sebenarnya ditanyakan, dan penjelasannya bagus. Saya menerima jawaban ini dan juga +1. Terima kasih.
Bhesh Gurung

42

Salah satu cara untuk menanganinya untuk menghapus sesuatu dari salinan Collection(bukan Koleksi itu sendiri), jika berlaku. Clonekoleksi asli untuk membuat salinan via a Constructor.

Pengecualian ini dapat dilemparkan oleh metode yang telah mendeteksi modifikasi bersamaan dari suatu objek ketika modifikasi tersebut tidak diizinkan.

Untuk kasus spesifik Anda, pertama, saya tidak berpikir finaladalah cara untuk pergi mengingat Anda berniat untuk mengubah daftar pernyataan terakhir

private static final List<Integer> integerList;

Juga pertimbangkan untuk memodifikasi salinan daripada daftar aslinya.

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) {
    if(integer.equals(remove)) {                
        copy.remove(integer);
    }
}

14

Metode forward / iterator tidak berfungsi saat menghapus item. Anda dapat menghapus elemen tanpa kesalahan, tetapi Anda akan mendapatkan kesalahan runtime ketika Anda mencoba mengakses item yang dihapus. Anda tidak dapat menggunakan iterator karena seperti yang ditunjukkan oleh pushy akan menyebabkan ConcurrentModificationException, jadi gunakan regular untuk loop, tetapi mundurlah.

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

Sebuah solusi:

Lintasi array dalam urutan terbalik jika Anda akan menghapus elemen daftar. Cukup dengan menelusuri kembali daftar Anda menghindari mengunjungi item yang telah dihapus, yang menghilangkan pengecualian.

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) {
    if (integerList.get(i).equals(remove) ) {
        integerList.remove(i);
    }
}

ide cemerlang!
dobrivoje

7

Cuplikan ini akan selalu menampilkan ConcurrentModificationException.

Aturannya adalah "Anda tidak boleh memodifikasi (menambah atau menghapus elemen dari daftar) saat iterasi menggunakan Iterator (yang terjadi ketika Anda menggunakan untuk-setiap loop)".

JavaDocs:

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.

Karenanya, jika Anda ingin memodifikasi daftar (atau koleksi apa pun secara umum), gunakan iterator, karena kemudian menyadari modifikasi dan karenanya mereka akan ditangani dengan benar.

Semoga ini membantu.


3
OP dengan jelas menyatakan bahwa salah satu loop TIDAK melempar pengecualian dan yang sombong adalah mengapa hal itu terjadi.
madth3

apa yang kamu maksud dengan 'sombong'?
Bhushan

4

Saya memiliki masalah yang sama tetapi jika saya menambahkan elemen en ke dalam daftar iterated. Saya membuatnya seperti ini

public static void remove(Integer remove) {
    for(int i=0; i<integerList.size(); i++) {
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove)) {                
            integerList.remove(i);
        }
    }
}

Sekarang semuanya berjalan dengan baik karena Anda tidak membuat iterator di daftar Anda, Anda mengulanginya "secara manual". Dan kondisi i < integerList.size()tidak akan pernah membodohi Anda karena ketika Anda menghapus / menambahkan sesuatu ke dalam ukuran Daftar dari penurunan / kenaikan Daftar.

Semoga ini bisa membantu, bagi saya itu solusinya.


Ini tidak benar ! Bukti: jalankan cuplikan ini untuk melihat hasilnya: public static void main (String ... args) {Daftar <String> listOfBooks = new ArrayList <> (); listOfBooks.add ("Kode Lengkap"); listOfBooks.add ("Kode 22"); listOfBooks.add ("22 Efektif"); listOfBooks.add ("Netbeans 33"); System.err.println ("Sebelum menghapus:" + listOfBooks); untuk (int index = 0; index <listOfBooks.size (); index ++) {if (listOfBooks.get (index) .contains ("22")) {listOfBooks.remove (index); }} System.err.println ("Setelah menghapus:" + listOfBooks); }
dobrivoje

1

Jika Anda menggunakan koleksi copy-on-write itu akan berfungsi; namun ketika Anda menggunakan list.iterator (), Iterator yang dikembalikan akan selalu merujuk koleksi elemen seperti ketika (seperti di bawah) list.iterator () dipanggil, bahkan jika utas lain memodifikasi koleksi. Metode mutasi apa pun yang dipanggil pada Iterator atau ListIterator berbasis copy-on-write (seperti menambah, mengatur, atau menghapus) akan melempar UnsupportedOperationException.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

0

Ini berjalan dengan baik di Java 1.6

~% javac RemoveListElementDemo.java
~% java RemoveListElementDemo
~% cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo {    
    private static final List<Integer> integerList;

    static {
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    }

    public static void remove(Integer remove) {
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }

    public static void main(String... args) {                
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) {
            if(integer.equals(remove)) {                
                integerList.remove(integer);
            }
        }
    }
}

~%


Maaf tentang kesalahan ketik Ini 'berjalan' baik di Jawa 1.6
battosai

Hmm ... Mungkin Anda memiliki implementasi yang berbeda. Tetapi menurut spesifikasi itu seharusnya melakukan itu, IMO. Lihatlah jawaban @ Pushy.
Bhesh Gurung

sayangnya, id tidak ada di java 1.8
dobrivoje

0

Dalam kasus saya, saya melakukannya seperti ini:

int cursor = 0;
do {
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
} while (cursor != integerList.size());

0

Ubah Iterator for eachmenjadi for loopsolusi.

Dan Alasannya adalah:

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.

--Documents Java yang dirujuk.


-1

Periksa pembuat kode Anda ....

Dalam metode utama Anda mencoba untuk menghapus elemen ke-4 yang tidak ada di sana dan karenanya kesalahan. Dalam metode hapus () Anda mencoba menghapus elemen ke-3 yang ada dan karenanya tidak ada kesalahan.


Anda salah: angka 2dan 3bukan indeks untuk daftar, tetapi elemen. Kedua logika penghapusan memeriksa equalsterhadap elemen daftar, bukan indeks elemen. Lebih jauh lagi, jika itu terkait dengan indeks, itu akan menjadi IndexOutOfBoundsException, tidak ConcurrentModificationException.
Malte Hartwig
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.