java循環(huán)刪除List元素報錯的原因分析與解決
描述
大家在工作中應該都會遇到從List集合中刪除某一個或多個元素的業(yè)務場景
相信大家都會避開在循環(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);
}
}
//就拋異常了,因為Iterator的next方法會校驗是否修改
//此時: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方法會校驗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方法,導致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)里面刪除報錯也是同樣的原理
往往大家在遇到問題后,都只是找到了主要原因,但是并沒有找到根本原因。
只有通過深入分析找到根本原因,制定預防措施(加入CR清單,添加掃碼規(guī)則,制定獎懲措施),才能夠真正避免問題
到此這篇關于java循環(huán)刪除List元素報錯的原因分析與解決的文章就介紹到這了,更多相關java循環(huán)刪除元素內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java面向?qū)ο缶幊蹋ǚ庋b/繼承/多態(tài))實例解析
這篇文章主要介紹了Java面向?qū)ο缶幊蹋ǚ庋b/繼承/多態(tài))實例解析的相關內(nèi)容,具有一定參考價值,需要的朋友可以了解下。2017-10-10
詳解OpenAPI開發(fā)如何動態(tài)的添加接口實現(xiàn)
這篇文章主要為大家介紹了OpenAPI開發(fā)如何動態(tài)的添加接口實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
HashMap在JDK7與JDK8中的實現(xiàn)過程解析
這幾天學習了HashMap的底層實現(xiàn),但是發(fā)現(xiàn)好幾個版本的,代碼不一,很多文章都是舊版本JDK1.6.JDK1.7的?,F(xiàn)在我來分析下JDK7與JDK8中HashMap的實現(xiàn)過程2021-09-09
利用JAVA反射,讀取數(shù)據(jù)庫表名,自動生成對應實體類的操作
這篇文章主要介紹了利用JAVA反射,讀取數(shù)據(jù)庫表名,自動生成對應實體類的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
SpringBoot?@Configuration與@Bean注解使用介紹
這篇文章主要介紹了SpringBoot中的@Configuration與@Bean注解,在進行項目編寫前,我們還需要知道一個東西,就是SpringBoot對我們的SpringMVC還做了哪些配置,包括如何擴展,如何定制,只有把這些都搞清楚了,我們在之后使用才會更加得心應手2022-10-10

