java循環(huán)刪除List元素報錯的原因分析與解決
描述
大家在工作中應(yīng)該都會遇到從List集合中刪除某一個或多個元素的業(yè)務(wù)場景
相信大家都會避開在循環(huán)里面刪除元素,使用其他方式處理
很多面試官也都會問為什么循環(huán)里面不能刪除元素?
示例
public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("test0"); list.add("test1"); list.add("test2"); list.add("test3"); list.add("test4"); list.add("test5"); System.out.println(list); list.remove(3); System.out.println(list); list.remove("test1"); System.out.println(list); for (int i = 0; i < list.size(); i++) { if (i == 2) { list.remove(i); } } System.out.println(list); Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("test2")) { it.remove(); } } System.out.println(list); for (String s : list) { if ("test5".equals(s)) { list.remove(s); } } System.out.println(list); } //打印結(jié)果 [test0, test1, test2, test3, test4, test5] [test0, test1, test2, test4, test5] [test0, test2, test4, test5] [test0, test2, test5] [test0, test5] Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.fc.store.Test.main(Test.java:46)
從打印結(jié)果可以看到
- 根據(jù)索引刪除 --正常
- 根據(jù)元素刪除 --正常
- 循環(huán)根據(jù)索引刪除 --正常
- 迭代刪除原始 --正常
- 循環(huán)根據(jù)元素刪除 --不正常
執(zhí)行過程
List<String> list = new ArrayList<>(); //集合初始化時 transient Object[] elementData; //空數(shù)組 private int size; //長度為0 protected transient int modCount = 0; //修改次數(shù)為0 //添加元素 list.add("test0"); elementData[0] = "test0"; size = 1; modCount = 1; list.add("test1"); elementData[0] = "test0"; elementData[1] = "test1"; size = 2; modCount = 2; list.add("test2"); elementData[0] = "test0"; elementData[1] = "test1"; elementData[2] = "test2"; size = 3; modCount = 3; list.add("test3"); elementData[0] = "test0"; elementData[1] = "test1"; elementData[2] = "test2"; elementData[3] = "test3"; size = 4; modCount = 4; list.add("test4"); elementData[0] = "test0"; elementData[1] = "test1"; elementData[2] = "test2"; elementData[3] = "test3"; elementData[4] = "test4"; size = 5; modCount = 5; list.add("test5"); elementData[0] = "test0"; elementData[1] = "test1"; elementData[2] = "test2"; elementData[3] = "test3"; elementData[4] = "test4"; elementData[5] = "test5"; size = 6; modCount = 6; //可以發(fā)現(xiàn)每添加一個元素,集合size會增加1, 修改次數(shù)會增加1 //根據(jù)索引刪除 list.remove(3); elementData[0] = "test0"; elementData[1] = "test1"; elementData[2] = "test2"; elementData[4] = "test4"; elementData[5] = "test5"; size = 5; modCount = 7; //根據(jù)元素刪除 list.remove("test1"); elementData[0] = "test0"; elementData[1] = "test2"; elementData[2] = "test4"; elementData[5] = "test5"; size = 4; modCount = 8; //循環(huán)根據(jù)索引刪除 for (int i = 0; i < list.size(); i++) { if (i == 2) { list.remove(i); } } elementData[0] = "test0"; elementData[1] = "test2"; elementData[5] = "test5"; size = 3; modCount = 9; //循環(huán)根據(jù)索引刪除 Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("test2")) { it.remove(); } } elementData[0] = "test0"; elementData[5] = "test5"; size = 2; modCount = 10; //可以發(fā)現(xiàn)每刪除一個元素后,集合size會減1,修改次數(shù)會增加1 //循環(huán)根據(jù)元素刪除 for (String s : list) { if ("test2".equals(s)) { list.remove(s); } } //就拋異常了,因?yàn)镮terator的next方法會校驗(yàn)是否修改 //此時:expectedModCount = 10, modCount = 11 public E next() { checkForComodification(); } final void checkForComodification() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
源碼解析
List 的add和remove方法每次操作都會對modCount++
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) { if (elementData[index] == null) { fastRemove(index); return true; } } } else { for (int index = 0; index < size; index++) { if (o.equals(elementData[index])) { fastRemove(index); return true; } } } return false; } private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private void ensureExplicitCapacity(int minCapacity) { modCount++; }
Iterator 會繼承List的modCount,并賦值給expectedModCount
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; }
Iterator 的next方法會校驗(yàn)modCount和expectedModCount 是否相同
public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
由于在Iterator的便利中使用了List的remove方法,導(dǎo)致modCount增加了 所以在下次next方法中判斷modCount和expectedModCount不一致就直接拋出了異常
解決方案
第一種使用迭代器刪除
Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("test2")) { it.remove(); } }
第二種for循環(huán)刪除后要立即退出
for (String s : list) { if ("test5".equals(s)) { list.remove(s); return; } } //JAVA8語法 list.removeIf("test5"::equals);
Set,Map 同理
同樣Set,Map循環(huán)里面刪除報錯也是同樣的原理
往往大家在遇到問題后,都只是找到了主要原因,但是并沒有找到根本原因。
只有通過深入分析找到根本原因,制定預(yù)防措施(加入CR清單,添加掃碼規(guī)則,制定獎懲措施),才能夠真正避免問題
到此這篇關(guān)于java循環(huán)刪除List元素報錯的原因分析與解決的文章就介紹到這了,更多相關(guān)java循環(huán)刪除元素內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java因項(xiàng)目配置不當(dāng)而引發(fā)的數(shù)據(jù)泄露
這篇文章主要介紹了Java因項(xiàng)目配置不當(dāng)而引發(fā)的數(shù)據(jù)泄露解決辦法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-09-09利用spring aop實(shí)現(xiàn)動態(tài)代理
這篇文章主要為大家詳細(xì)介紹了利用spring aop實(shí)現(xiàn)動態(tài)代理的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03MybatisPlus?自定義插件實(shí)現(xiàn)攔截SQL修改功能(實(shí)例詳解)
這篇文章主要介紹了MybatisPlus?自定義插件實(shí)現(xiàn)攔截SQL修改功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-11-11SpringBoot父子線程數(shù)據(jù)傳遞的五種方案介紹
在實(shí)際開發(fā)過程中我們需要父子之間傳遞一些數(shù)據(jù),比如用戶信息等。該文章從5種解決方案解決父子之間數(shù)據(jù)傳遞困擾,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-09-09基于@Valid和@Validated驗(yàn)證List集合的踩坑記錄
這篇文章主要介紹了基于@Valid和@Validated驗(yàn)證List集合的踩坑記錄,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07Java中RSA加密解密的實(shí)現(xiàn)方法分析
這篇文章主要介紹了Java中RSA加密解密的實(shí)現(xiàn)方法,結(jié)合具體實(shí)例形式分析了java實(shí)現(xiàn)RSA加密解密算法的具體步驟與相關(guān)操作技巧,并附帶了關(guān)于RSA算法密鑰長度/密文長度/明文長度的參考說明,需要的朋友可以參考下2017-07-07hibernate-validator如何使用校驗(yàn)框架
高效、合理的使用hibernate-validator校驗(yàn)框架可以提高程序的可讀性,以及減少不必要的代碼邏輯,本文主要介紹了hibernate-validator如何使用校驗(yàn)框架,感興趣的可以了解一下2022-04-04