问题案例分析
public class ArrayListExceptionTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
/*for (String s : list) {
if ("2".equals(s)){
list.remove(s);
}
}*/
for (String s : list) {
if ("1".equals(s)){
list.remove(s);
}
}
}
}
结果1:我们发现删除“1”的时候会报一个并发修改的异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at ArrayListExceptionTest.EqualUnderstand.main(EqualUnderstand.java:38)
结果2:当我们删除“2”的时候,我们发现可以正常删除,却不报错
当我们更换数组里的大小和内容时,我们发现使用ForEach遍历数组时,只能删除倒数第二个,删除其他则报错!!!这是什么原因呢?
ForEach底层使用迭代器进行遍历的,:它会先指针移动来判断是否当前的数组是否有值,再去获取下一个值
下面是伪代码:
- array:表示遍历的数组
- hasNext():判断下一个位置是否还有值得方法
- next():将指针移动到下一个位置,并且返回当前指针所在位置的值
while(array.haxNext()){
int value = array.next();
}
下面我们来看看遍历的源码:
如图进行以下解释:
变量:
- cursor :索引或者指针,表示指向当前遍历元素的位置记录,初始值为0
- size:记录集合元素的个数
- modCount:实际修改的次数
- expectedModCount: 期待修改的次数
方法:
- checkForComodification: 检查是否出现了并发修改异常(根据modCount != expectedModCount)
- hasNext: 判断是否有下一个元素,也就是判断是否元素已经遍历完了(根据cursor != size)
- next:检查是否出现了并发修改异常,cursor变量值+1,获取下一个元素内容
下面我们来看看删除的源码:
方法:
- remove:
查询要删除元素的索引位置
调用fastRemove方法 - fastRemove:
进行索引范围的边界检查
modCount变量值+1
进行删除元素位置的迁移拷贝覆盖
所以每次当我们执行删除时执行remove(object)方法,modCount+1
然后再去执行next方法就会导致expectedModCount != ArrayList.this.modCount,抛出并发修改异常
那么为什么删除倒数第二个元素的时候不会报错,让我们看看下面这代码执行流程图的解释:
我们假设原始的数量为original,实际数量为size
当删除倒数第二个元素的时候,cursor = original -1 size =original - 1
当遍历到最后一个元素的时候,cursor = size,表示遍历结束没有下一个元素
为什么使用iter迭代器删除,却不会报错呢?
案例:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if ("2".equals(s)){
iterator.remove();
}
}
}
我们发现无论删除什么元素,都是不会报错的!!!!
我们点击remove查看源码:
我发现调用remove方法时,直接赋值修改·expectedModCount = modCount
,这样在进行next()检查时,就不会抛出并发修改异常了!!