Rumah >Java >javaTutorial >Bagaimana untuk mengelakkan pengecualian dalam gelung mudah dalam JAVA?
Dalam pembangunan projek perniagaan sebenar, semua orang harus biasa dengan operasi mengalih keluar elemen yang tidak memenuhi syarat dari senarai yang diberikan, bukan?
Ramai pelajar boleh segera memikirkan pelbagai cara untuk mencapainya, tetapi adakah semua cara yang anda fikirkan tidak berbahaya kepada manusia dan haiwan? Banyak operasi yang kelihatan normal sebenarnya adalah perangkap, dan ramai orang baru mungkin terjerumus ke dalamnya jika mereka tidak berhati-hati.
Jika malangnya anda memijak:
Kod akan secara langsung membuang pengecualian dan melaporkan ralat apabila berjalan Ini adalah rahmat dalam kemalangan ditemui dan diselesaikan dalam masa
Kod berjalan tanpa kesilapan, tetapi pelbagai masalah pelik muncul secara tidak dapat dijelaskan dalam logik perniagaan Ini lebih tragis, kerana jika masalah ini tidak diberi perhatian, ia boleh menyebabkan bahaya tersembunyi untuk perniagaan seterusnya.
Jadi, apakah kaedah pelaksanaannya? Kaedah pelaksanaan yang manakah mungkin mempunyai masalah? Mari kita bincangkan bersama di sini. Sila ambil perhatian bahawa apa yang dibincangkan di sini bukanlah isu bagaimana menulis perkataan "adas" dalam kacang adas, tetapi isu teknikal yang sangat serius dan praktikal yang mudah diabaikan.
Anggap senario permintaan:
Memandangkan senarai pengguna semuaPengguna, adalah perlu untuk mengalih keluar kakitangan yang jabatannya adalah pembangun daripada senarai, dan mengembalikan maklumat kakitangan yang tinggal kepada
Pemikiran pertama ramai orang baru ialah menyemak gelung for satu persatu dan kemudian menghapuskan yang memenuhi syarat~ begitu mudah.. .
Saya selesai menulis kod dalam masa 1 minit:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { for (UserDetail user : allUsers) { // 判断部门如果属于dev,则直接剔除 if ("dev".equals(user.getDepartment())) { allUsers.remove(user); } } // 返回剩余的用户数据 return allUsers; }
Kemudian saya mengklik butang laksana dengan yakin:
java.util.ConcurrentModificationException: null at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.veezean.demo4.UserService.filterAllDevDeptUsers(UserService.java:13) at com.veezean.demo4.Main.main(Main.java:26)
Eh? awak tengah buat apa? Mengapa pengecualian dilemparkan?
Saya melangkah ke dalam lubang tanpa memberi perhatian. Mari kita analisa mengapa pengecualian dilemparkan.
Analisis sebab:
Pemprosesan sebenar sintaks foreach JAVA adalah berdasarkan Iterator Iterator.
Pada permulaan gelung, tika lelaran akan dibuat terlebih dahulu dan expectedModCount bagi tika lelaran ini diberikan modCount koleksi. Setiap kali iterator menggunakan hashNext() / next() untuk melintasi elemen seterusnya, ia akan menyemak sama ada pembolehubah modCount dan nilai expectedModCount adalah sama menamatkan perjalanan.
Jika anda menambah atau memadam elemen dalam gelung, anda terus memanggil kaedah add() dan remove() koleksi, menyebabkan modCount bertambah atau berkurang, tetapi kaedah ini tidak akan mengubah suai expectedModCount dalam contoh lelaran, menghasilkan Jika nilai expectedModCount dan modCount dalam contoh lelaran tidak sama, pengecualian ConcurrentModificationException dilemparkan.
Hmm? Oleh kerana kaedah foreach tidak berfungsi, maka gunakan kaedah gelung subskrip asal untuk melakukannya Anda tidak akan mendapat ralat, bukan? Ia masih sangat mudah...
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { for (int i = 0; i < allUsers.size(); i++) { // 判断部门如果属于dev,则直接剔除 if ("dev".equals(allUsers.get(i).getDepartment())) { allUsers.remove(i); } } // 返回剩余的用户数据 return allUsers; }
Kod selesai dalam sekali jalan dan lihat output yang diproses:
{id=2, name='李.思', jabatan ='dev'}
{id=3, name='王五', department='product'}
{id=4, name='Tiezhu', department='pm'}
Sudah pasti, tiada ralat dilaporkan dan hasilnya adalah output, sempurna~
Tunggu? Adakah ini benar-benar OK?
Logik kod kami adalah untuk menilai jika "dev".sama dengan(jabatan), tetapi dalam hasil output, mengapa masih terdapat data seperti department=dev yang harus dihapuskan?
Jika ini adalah projek perniagaan sebenar, jika tiada ralat dilaporkan semasa fasa pembangunan, dan hasilnya tidak disahkan dengan teliti, dan kemudian mengalir ke barisan pengeluaran, ia mungkin menyebabkan logik perniagaan yang tidak normal.
Mari kita lihat sebab khusus fenomena ini.
Analisis sebab:
Kami tahu bahawa sebenarnya tidak ada hubungan ikatan yang kuat antara elemen dalam senarai dan subskrip, ia hanyalah hubungan tertib kedudukan yang sepadan Selepas elemen dalam list ditukar , subskrip yang sepadan dengan setiap elemen mungkin berubah, seperti yang ditunjukkan di bawah:
Kemudian, selepas memadamkan elemen daripada Senarai, semua elemen selepas elemen yang dipadamkan dalam Senarai Subskrip dialihkan ke hadapan, tetapi penunjuk i gelung for sentiasa terkumpul ke belakang Apabila memproses yang seterusnya, beberapa elemen mungkin terlepas dan tidak diproses.
Sebagai contoh, seperti yang ditunjukkan dalam rajah di bawah, apabila i=0, ia dinilai bahawa elemen A perlu dipadamkan, dan ia dipadamkan secara langsung apabila mengitar semula, i=1, pada masa ini; kerana kedudukan elemen dalam senarai bergerak ke hadapan, elemen B menjadi Kedudukan asal dengan subskrip 0 langsung terlepas:
Jadi pada ketika ini, anda boleh tahu mengapa kod di atas akan terlepas selepas pelaksanaan La~
Selepas melihat dua perangkap di atas, apakah cara operasi yang betul dan sesuai?
Eh? Betul ke? Bukankah saya hanya mengatakan bahawa kaedah foreach juga menggunakan iterator, tetapi adakah ia sebenarnya operasi perangkap? Mengapa dikatakan di sini bahawa corak iterator adalah cara yang betul?
虽然都是基于迭代器,但是使用逻辑是不一样的,看下代码:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { Iterator<UserDetail> iterator = allUsers.iterator(); while (iterator.hasNext()) { // 判断部门如果属于dev,则直接剔除 if ("dev".equals(iterator.next().getDepartment())) { // 这是重点,此处操作的是Iterator,而不是list iterator.remove(); } } // 返回剩余的用户数据 return allUsers; }
执行结果:
{id=3, name='王五', department='product'}
{id=4, name='铁柱', department='pm'}
这次竟然直接执行成功了,且结果也是正确的。为啥呢?
在前面foreach方式的时候,我们提过之所以会报错的原因,是由于直接修改了原始list数据而没有同步让Iterator感知到,所以导致Iterator操作前校验失败抛异常了。
而此处的写法中,直接调用迭代器中的remove()方法,此操作会在调用集合的remove(),add()方法后,将expectedModCount重新赋值为modCount,所以在迭代器中增加、删除元素是可以正常运行的。,所以这样就不会出问题啦。
言简意赅,直接上代码:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { allUsers.removeIf(user -> "dev".equals(user.getDepartment())); return allUsers; }
作为JAVA8开始加入的Stream,使得这种场景实现起来更加的优雅与易懂:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { return allUsers.stream() .filter(user -> !"dev".equals(user.getDepartment())) .collect(Collectors.toList()); }
既然前面说了不能直接循环的时候执行移除操作,那就先搞个list对象将需要移除的元素暂存起来,最后一起剔除就行啦 ~
嗯,虽然有点挫,但是不得不承认,实际情况中,很多人都在用这个方法 —— 说的就是你,你是不是也曾这么写过?
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { List<UserDetail> needRemoveUsers = new ArrayList<>(); for (UserDetail user : allUsers) { if ("dev".equals(user.getDepartment())) { needRemoveUsers.add(user); } } allUsers.removeAll(needRemoveUsers); return allUsers; }
或者:
public List<UserDetail> filterAllDevDeptUsers(List<UserDetail> allUsers) { List<UserDetail> resultUsers = new ArrayList<>(); for (UserDetail user : allUsers) { if (!"dev".equals(user.getDepartment())) { resultUsers.add(user); } } return resultUsers; }
Atas ialah kandungan terperinci Bagaimana untuk mengelakkan pengecualian dalam gelung mudah dalam JAVA?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!