以collections的子类List(Arraylist)为例
List<String> list=new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); for (String s:list) { if (s.equals("d")) { list.remove(s); } } System.out.println(list);
做一个遍历的操作,如果list中的某个元素满足某个条件,则将该元素从list中移除。
经过测试运行,可以发现,除非说要移除的元素位于倒数第二的元素,否则会出现异常,报错如下:
Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at Main.main(Main.java:49)
再看如下一例子
List<String> list=new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("c"); for (String s:list) { if (s.equals("c")) { list.remove(s); } } System.out.println(list);
期望得到的list应该只包含“a”,“b”两个元素,然而得到的是如下的list:
[a, b, c]
解决办法:
定义一个新的list,将要删除的list放在这个新定义的list中,最后调用removeAll的方法整体删除
List<String> dele=new ArrayList<>(); List<String> list=new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("c"); for (String s:list) { if (s.equals("c")) { dele.add(s); } } list.removeAll(dele); System.out.println(list);
错误分析:
首先知道for—each循环是使用迭代器来实现的,找到源码
// 在AbstractList类中 // 内部成员有: // protected transient int modCount = 0; // 注:该modCount相当于集合的版本号,集合内容每改变一次,其值就+1。 // 观察下面内部类可以知道,在next()、remove()方法都会去判断集合版本号与迭代器版本号是否一致checkForComodification(); // 内部方法有: public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; //***创建迭代器时就讲版本号给了迭代器 int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet == -1) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount;//迭代器进行删除时,会改变it的版本 } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } //检查迭代器的版本与集合的版本是否一致,不同则抛出异常 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
需要知道两点:
1.modCount,每一次list改变都会进行使其+1
2.cusor,光标,新遍历元素时候+1
该案列中,list.size()=4,遍历集合时创建集合迭代器,此时有 expectedModCount = modCount=4,迭代器的cursor=0,
(1)cursor=0,it.next()检查版本号一致,遍历第一个元素,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true
(2)cursor=1,it.next()检查版本号一致,遍历第二个元素,cursor=cursor+1=2,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true
(3)cursor=2,it.next()检查版本号一致,遍历第三个元素,cursor=cursor+1=3,进入if语句,移除一个元素,此时集合内容改变,版本号modCount++,为5.然后it.hasNext()判断cursor(2)!=size(4),true
(3)cursor=3,it.next()检查发现版本号不一致expectedModCount! = modCount,抛出并发修改异常。
而我们所举的第二个例子可以看出,若要删除的元素位置在倒数第二个中,则遍历会提前结束,原因:
(1)cursor=0,it.next()检查版本号一致,遍历第一个元素,cursor=cursor+1=1,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true
(2)cursor=1,it.next()检查版本号一致,遍历第二个元素,cursor=cursor+1=2,打印,然后it.hasNext()函数判断cursor!=list.size(),返回true
(3)cursor=3,it.next()检查版本号一致,遍历到要删除的元素,cursor=cusor+1=3;进入if语句,list.remove(),此时,版本号modCount++,且集合大小改变list.size()=3,然后,it.hasNext()判断发现cursor(3)==size(3),返回false,迭代提前结束
因此不会爆出异常