分析ArrayList在遍历时修改报错的原因
Posted LiuJun2Son
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分析ArrayList在遍历时修改报错的原因相关的知识,希望对你有一定的参考价值。
以前使用for(:)遍历List集合并同时修改List集合中的内容时会报:ConcurrentModificationException错误,这个错误就是提示我们:方法中有对象的并发修改,但不允许这种修改时,所以抛出此异常。
1.模拟遍历集合的同时修改集合(一)
新建一个list集合,并给该集合依次添加1-5的字符窜,然后遍历集合,在遍历集合的时候删除”2”
public static void main(String[] args)
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
System.out.println("原来的list:" + list);
for (String string : list)
System.out.println(string);
//如果获取的内容是"2",就把它删除
if ("2".equals(string))
list.remove(string);
System.out.println("修改后的list:: " + list);
输出的结果:
原来的list:[1, 2, 3, 4, 5]
1
2
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at com.test.jh.TestListDemo.main(TestListDemo.java:16)
当集合遍历到”2”这个元素的时候,就出现了ConcurrentModificationException异常,拿到底是哪一个方法抛出的异常?其实是Iterator类中next()的方法抛出的异常。
2.foreach遍历集合的原理分析
通过反编译技术,反编译上面mian方法中的代码
public static void main(String args[])
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
//1.字符串通过+拼接的底层实现也是通过StringBuilder这个类
System.out.println((new StringBuilder("原来的list:")).append(list).toString());
//2.使用for (String string : list) 遍历集合的底层是使用Iterators实现
for (Iterator iterator = list.iterator(); iterator.hasNext();)
//3.重点:这里调用了next()方法,其实上面的那个错误就是调用了这个方法报错的
String string = (String)iterator.next();
System.out.println(string);
if ("2".equals(string))
list.remove(string);
System.out.println((new StringBuilder("修改后的list:: ")).append(list).toString());
同过分析foreach遍历集合的原理,遍历集合的底层是使用Iterators实现,并且报错是因为执行了String string = (String)iterator.next();这一行代码
3.ArrayList的源码分析
查看Iterator中的next()方法
因为报错的原因是执行了:String string = (String)iterator.next();这句,那查看Iterator类的代码
//把集合修改的次数(modCount)赋值给expectedModCount记录下来
int expectedModCount = modCount;
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()
//modCount代表集合修改的次数(例如:每list.add()一次就加1,list.remove()一次也加1)
//expectedModCount的值是等于开始遍历集合时的修改的次数
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
checkForComodification()方法中的modCount与expectedModCount的值不相等就会抛出异常。那么他们两个值分别在哪里被修改了呢?其实modCount的值是在list集合执行add,remove等操作的时候赋值,而expectedModCount是在new Iterator()时,把modCount的值赋给了expectedModCount记录。
查看ArrayList类的源码
那查看ArrayList类的remove()代码,发现remove方法中真的有对modCount++
public E remove(int index)
rangeCheck(index);
//记录了集合修改的次数,其实集合中的add()方法中也有modCount++;的代码
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;
聪明的你,从上面的代码就可以知道,当我们遍历集合的并修改集合的内容时,为什么会导致checkForComodification()中的值不相等就会抛出ConcurrentModificationException异常。
4.遍历List集合并同时修改List集合中的内容报错的原因
public static void main(String args[])
List list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
System.out.println((new StringBuilder("原来的list:")).append(list).toString());
//3.重点:list.iterator()
for (Iterator iterator = list.iterator(); iterator.hasNext();)
String string = (String)iterator.next();
System.out.println(string);
if ("2".equals(string))
list.remove(string);
System.out.println((new StringBuilder("修改后的list:: ")).append(list).toString());
1.当代码在遍历的时候执行到list.iterator()时候就创建了一个Iterator对象,并在创建这个Iterator对象的时候modCount的值赋值给了expectedModCount,这时expectedModCount的值就是集合当前修改的次数。
//执行list.iterator()调用了这个方法
public Iterator<E> iterator()
return new Itr();
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
//创建这个Iterator对象的时候modCount的值赋值给了expectedModCount,这时expectedModCount的值就是集合当前修改的次数
int expectedModCount = modCount;
public boolean hasNext()
return cursor != size;
.....
2.当代码在执行到下面的这个语句的时候,执行了list.remove(),这个时候modCount++;导致了modCount与expectedModCount不相等。那些下一次的循环在调用String string = (String)iterator.next();的时候就会导致next()方法中的checkForComodification()方法中的modCount与expectedModCount的值不相等就会抛出异常ConcurrentModificationException。
if ("2".equals(string))
list.remove(string);
总结
其实出现异常就是我们在遍历集合的时候修改了集合的内容,导致modCount的值在增加,而expectedModCount得值没有增加,所以在调用next()方法中的checkForComodification()方法判断集合的修改次数是否合法就会报错。
6.模拟遍历集合的同时修改集合(二)
新建一个list集合,并给该集合依次添加1-5的字符窜,然后遍历集合,在遍历集合的时候删除”4”(倒数第二个)
public static void main(String[] args)
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
System.out.println("原来的list:" + list);
for (String string : list)
System.out.println(string);
//重点:是删除"4",而不是"2",那么会报错吗?其实不会
if ("4".equals(string))
list.remove(string);
System.out.println("修改后的list:: " + list);
输出的结果是:
原来的list:[1, 2, 3, 4, 5]
1
2
3
4
修改后的list:: [1, 2, 3, 5]
从上面的可以看出,靠,居然没有报错成功删除了?这是什么情况?
分析原因
1.当执行到下面的代码的时候,list.remove()删除了一个元素,导致size也减少了1。
//当前的cursor=4,size=5
if ("4".equals(string))
list.remove(string);//删除了一个size-1等于4
2.接着当增强for循环再次判断hasNext():
public boolean hasNext()
//当遍历到"4"的时候,当前的cursor=4,当前的size=4
return cursor != size;
3.当执行到for (Iterator iterator = list.iterator(); iterator.hasNext();)的iterator.hasNext()时,返回的值是false。为什么呢?当遍历到”4”的时候,当前的cursor=4,size=4,导致hasNext()返回的值是false导致直接退出了for循环,漏掉了第五次循环(第五次也是最后一个循环),导致没有执行String string = (String)iterator.next();就没有报错。
4.如果我们删除的那个不是”4”,而是”3”,结果导致当前的cursor=3,当前的size=4,当下一次执行iterator.hasNext()判断时,返回的结果是true,导致String string = (String)iterator.next();会执行,删除一个元素后,导致modCount的值在增加,而expectedModCount得值没有增加,所以在调用next()方法中的checkForComodification()方法判断集合的修改次数是否合法就会报错。
以上是关于分析ArrayList在遍历时修改报错的原因的主要内容,如果未能解决你的问题,请参考以下文章
ArrayList 使用 forEach 遍历时删除元素会报错吗?