Kita semua tahu Anda tidak dapat melakukan hal berikut karena ConcurrentModificationException
:
for (Object i : l) {
if (condition(i)) {
l.remove(i);
}
}
Tapi ini tampaknya bekerja kadang-kadang, tetapi tidak selalu. Ini beberapa kode spesifik:
public static void main(String[] args) {
Collection<Integer> l = new ArrayList<>();
for (int i = 0; i < 10; ++i) {
l.add(4);
l.add(5);
l.add(6);
}
for (int i : l) {
if (i == 5) {
l.remove(i);
}
}
System.out.println(l);
}
Ini, tentu saja, menghasilkan:
Exception in thread "main" java.util.ConcurrentModificationException
Meskipun beberapa utas tidak melakukannya. Bagaimanapun.
Apa solusi terbaik untuk masalah ini? Bagaimana saya bisa menghapus item dari koleksi dalam satu lingkaran tanpa membuang pengecualian ini?
Saya juga menggunakan arbitrer di Collection
sini, tidak harus ArrayList
, jadi Anda tidak bisa mengandalkan get
.
java
collections
iteration
Claudiu
sumber
sumber
Jawaban:
Iterator.remove()
aman, Anda bisa menggunakannya seperti ini:Perhatikan bahwa
Iterator.remove()
satu-satunya cara aman untuk mengubah koleksi selama iterasi; perilaku tidak ditentukan jika koleksi yang mendasarinya dimodifikasi dengan cara lain saat iterasi sedang berlangsung.Sumber: docs.oracle> The Collection Interface
Dan demikian pula, jika Anda memiliki
ListIterator
dan ingin menambahkan item, Anda dapat menggunakanListIterator#add
, untuk alasan yang sama Anda dapat menggunakanIterator#remove
- itu dirancang untuk memungkinkannya.Dalam kasus Anda Anda mencoba untuk menghapus dari daftar, tapi pembatasan yang sama berlaku jika mencoba untuk
put
menjadiMap
sementara iterasi isinya.sumber
iterator.next()
panggilan ke-loop? Jika tidak, bisakah seseorang menjelaskan alasannya?List.add
"opsional" dalam arti yang sama juga, tetapi Anda tidak akan mengatakan itu "tidak aman" untuk ditambahkan ke daftar.Ini bekerja:
Saya berasumsi bahwa karena foreach loop adalah gula sintaksis untuk iterasi, menggunakan iterator tidak akan membantu ... tetapi memberi Anda
.remove()
fungsi ini .sumber
Dengan Java 8 Anda dapat menggunakan metode baru
removeIf
. Diterapkan pada contoh Anda:sumber
removeIf
menggunakanIterator
danwhile
loop. Anda dapat melihatnya di java 8java.util.Collection.java
ArrayList
karena alasan kinerja. Yang Anda maksud hanyalah implementasi standar.equals
tidak digunakan sama sekali di sini, jadi tidak harus diimplementasikan. (Tapi tentu saja, jika Anda menggunakanequals
dalam tes Anda maka itu harus dilaksanakan dengan cara yang Anda inginkan.)Karena pertanyaan telah dijawab yaitu cara terbaik adalah menggunakan metode hapus dari objek iterator, saya akan pergi ke spesifik tempat di mana kesalahan
"java.util.ConcurrentModificationException"
dilemparkan.Setiap kelas koleksi memiliki kelas pribadi yang mengimplementasikan antarmuka Iterator dan menyediakan metode seperti
next()
,remove()
danhasNext()
.Kode untuk selanjutnya terlihat seperti ini ...
Di sini metode
checkForComodification
diimplementasikan sebagaiJadi, seperti yang Anda lihat, jika Anda secara eksplisit mencoba menghapus elemen dari koleksi. Ini menghasilkan
modCount
perbedaan dariexpectedModCount
, menghasilkan pengecualianConcurrentModificationException
.sumber
Anda dapat menggunakan iterator secara 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 loop untuk-masing-masing 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 tua)
sumber
Dalam kasus seperti itu, trik umum adalah (dulu?) Untuk mundur:
Yang mengatakan, saya sangat senang bahwa Anda memiliki cara yang lebih baik di Java 8, misalnya
removeIf
ataufilter
di stream.sumber
ArrayList
koleksi s atau sejenisnya.for(int i = l.size(); i-->0;) {
?Jawaban yang sama dengan Claudius dengan for for:
sumber
Dengan Eclipse Collections , metode yang
removeIf
didefinisikan pada MutableCollection akan berfungsi:Dengan sintaks Java 8 Lambda ini dapat ditulis sebagai berikut:
Panggilan ke
Predicates.cast()
diperlukan di sini karenaremoveIf
metode default telah ditambahkan padajava.util.Collection
antarmuka di Java 8.Catatan: Saya pengendara untuk Eclipse Collections .
sumber
Buat salinan dari daftar yang ada dan ulangi salinan baru.
sumber
Orang-orang menyatakan seseorang tidak dapat menghapus dari Koleksi yang diulangi oleh loop foreach. Saya hanya ingin menunjukkan bahwa secara teknis tidak benar dan menjelaskan dengan tepat (saya tahu pertanyaan OP sangat canggih untuk menghindari mengetahui ini) kode di belakang asumsi itu:
Ini bukan berarti Anda tidak dapat menghapus dari iterasi
Colletion
daripada Anda tidak dapat melanjutkan iterasi setelah melakukannya. Karenanyabreak
dalam kode di atas.Mohon maaf jika jawaban ini agak khusus untuk kasus penggunaan dan lebih cocok untuk utas asli tempat saya tiba di sini, yang ditandai sebagai duplikat (meskipun utas ini tampak lebih bernuansa) dan dikunci.
sumber
Dengan loop tradisional
sumber
i++
di loop guard daripada di dalam loop body.i++
penambahan itu tidak bersyarat - saya mengerti sekarang itu sebabnya Anda melakukannya di dalam tubuh :)A
ListIterator
memungkinkan Anda untuk menambah atau menghapus item dalam daftar. Misalkan Anda memiliki daftarCar
objek:sumber
previous
metodenya.Saya punya saran untuk masalah di atas. Tidak perlu daftar sekunder atau waktu tambahan. Silakan temukan contoh yang akan melakukan hal yang sama tetapi dengan cara yang berbeda.
Ini akan menghindari Pengecualian Konkurensi.
sumber
ArrayList
dan dengan demikian tidak dapat diandalkanget()
. Kalau tidak, mungkin pendekatan yang baik.Collection
-Collection
antarmuka tidak termasukget
. (MeskipunList
antarmuka FWIW tidak termasuk 'dapatkan').while
-looping aList
. Tapi +1 untuk Jawaban ini karena itu yang lebih dulu.ConcurrentHashMap atau ConcurrentLinkedQueue atau ConcurrentSkipListMap dapat menjadi pilihan lain, karena mereka tidak akan pernah membuang ConcurrentModificationException, bahkan jika Anda menghapus atau menambahkan item.
sumber
java.util.concurrent
paket. Beberapa kelas kasus serupa / penggunaan umum lainnya dari paket itu adalahCopyOnWriteArrayList
&CopyOnWriteArraySet
[tetapi tidak terbatas pada itu].ConcurrentModificationException
, menggunakannya dalam loop -for- ditingkatkan masih dapat menyebabkan masalah pengindeksan (yaitu: masih melewatkan elemen, atauIndexOutOfBoundsException
...)Saya tahu pertanyaan ini terlalu tua untuk tentang Java 8, tetapi bagi mereka yang menggunakan Java 8 Anda dapat dengan mudah menggunakan removeIf ():
sumber
Cara lain adalah dengan membuat salinan arrayList Anda:
sumber
i
bukan objekindex
melainkan objek. Mungkin memanggilnyaobj
akan lebih pas.sumber
Dalam hal ArrayList: hapus (indeks int) - jika (indeks adalah posisi elemen terakhir) ia menghindari tanpa
System.arraycopy()
dan tidak membutuhkan waktu untuk ini.waktu arraycopy meningkat jika (indeks berkurang), dengan cara elemen-elemen daftar juga berkurang!
cara menghapus efektif terbaik adalah- menghapus elemen-elemennya dalam urutan menurun:
while(list.size()>0)list.remove(list.size()-1);
// take O (1)while(list.size()>0)list.remove(0);
// take O (factorial (n))sumber
Tangkapannya adalah setelah menghapus elemen dari daftar jika Anda melewatkan panggilan iterator.next internal (). masih bekerja! Meskipun saya tidak mengusulkan untuk menulis kode seperti ini, ada baiknya memahami konsep di baliknya :-)
Bersulang!
sumber
Contoh modifikasi pengumpulan aman thread:
sumber
Saya tahu pertanyaan ini mengasumsikan hanya
Collection
, dan tidak lebih spesifikList
. Tetapi bagi mereka yang membaca pertanyaan ini yang memang bekerja denganList
referensi, Anda dapat menghindariConcurrentModificationException
denganwhile
-loop (sambil memodifikasi di dalamnya) sebagai gantinya jika Anda ingin menghindariIterator
(baik jika Anda ingin menghindarinya secara umum, atau menghindarinya secara khusus untuk mencapai urutan pengulangan berbeda dari berhenti dari awal hingga akhir di setiap elemen [yang saya percaya adalah satu-satunya urutan yangIterator
dapat dilakukan sendiri)):* Pembaruan: Lihat komentar di bawah ini yang memperjelas analog juga dapat dicapai dengan loop tradisional .
Tidak ada ConcurrentModificationException dari kode itu.
Di sana kita melihat perulangan tidak dimulai di awal, dan tidak berhenti di setiap elemen (yang saya percaya
Iterator
tidak bisa lakukan).FWIW kita juga melihat
get
dipanggillist
, yang tidak dapat dilakukan jika referensi itu hanyaCollection
(bukanList
tipe -lebih spesifik dariCollection
) -List
antarmuka termasukget
, tetapiCollection
antarmuka tidak. Jika bukan karena perbedaan itu, makalist
rujukannya bisa menjadiCollection
[dan karena itu secara teknis Jawaban ini kemudian akan menjadi Jawaban langsung, bukan Jawaban tangensial].FWIWW kode yang sama masih berfungsi setelah dimodifikasi untuk mulai dari awal di berhenti di setiap elemen (seperti
Iterator
pesanan):sumber
ConcurrentModificationException
, tetapi bukan - untuk-loop tradisional (yang menggunakan Jawaban lain) - tidak menyadari bahwa sebelumnya adalah mengapa saya termotivasi untuk menulis Jawaban ini (saya keliru berpikir kemudian bahwa itu semua untuk loop yang akan membuang Pengecualian).Salah satu solusinya adalah dengan memutar daftar dan menghapus elemen pertama untuk menghindari ConcurrentModificationException atau IndexOutOfBoundsException
sumber
Coba yang ini (hapus semua elemen dalam daftar yang sama
i
):sumber
Anda juga dapat menggunakan Rekursi
Rekursi dalam java adalah suatu proses di mana suatu metode menyebut dirinya secara terus menerus. Metode dalam java yang menyebut dirinya disebut metode rekursif.
sumber
ini mungkin bukan cara terbaik, tetapi untuk sebagian besar kasus kecil ini harus dapat diterima:
Saya tidak ingat di mana saya membaca ini dari ... untuk keadilan saya akan membuat wiki ini dengan harapan seseorang menemukannya atau hanya untuk tidak mendapatkan perwakilan saya tidak pantas.
sumber