List list = new ArrayList();list.add("1");list.add("2");list.add("3");list.add("4");list.add("5");for (int i = 0; i
到底是什么原因呢?
由于两种遍历的方式不一样,结果就不一样,可以推测出问题可能出现在For-Each遍历上。
为了弄清楚这个问题,我们一步步的分析Java中ArrayList在遍历和删除的源代码。
1)直接进入ArrayList的源代码可以发现,在1.5的版本后,ArrayList中创建了一个内部迭代器Itr,并实现了Iterator接口,而For-Each遍历正是基于这个迭代器的hasNext()和next()方法来实现的;
先看一下这个内部迭代器:
/*** An optimized version of AbstractList.Itr*/private class Itr implements Iterator {int cursor; // index of next element to returnint lastRet &#61; -1; // index of last element returned; -1 if no suchint expectedModCount &#61; modCount;public boolean hasNext() {return cursor !&#61; size;}&#64;SuppressWarnings("unchecked")public E next() {checkForComodification();int i &#61; cursor;if (i >&#61; size)throw new NoSuchElementException();Object[] elementData &#61; ArrayList.this.elementData;if (i >&#61; elementData.length)throw new ConcurrentModificationException();cursor &#61; i &#43; 1;return (E) elementData[lastRet &#61; i];}public void remove() {if (lastRet <0)throw new IllegalStateException();checkForComodification();try {ArrayList.this.remove(lastRet);cursor &#61; lastRet;lastRet &#61; -1;expectedModCount &#61; modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}&#64;Override&#64;SuppressWarnings("unchecked")public void forEachRemaining(Consumer super E> consumer) {Objects.requireNonNull(consumer);final int size &#61; ArrayList.this.size;int i &#61; cursor;if (i >&#61; size) {return;}final Object[] elementData &#61; ArrayList.this.elementData;if (i >&#61; elementData.length) {throw new ConcurrentModificationException();}while (i !&#61; size && modCount &#61;&#61; expectedModCount) {consumer.accept((E) elementData[i&#43;&#43;]);}// update once at end of iteration to reduce heap write trafficcursor &#61; i;lastRet &#61; i - 1;checkForComodification();}final void checkForComodification() {if (modCount !&#61; expectedModCount)throw new ConcurrentModificationException();}}
这里有两个变量需要注意&#xff1a;
一个是modCount&#xff1a;这个外部的变量&#xff0c;也就是ArrayList下的变量&#xff1a;
/*** The number of times this list has been structurally modified.* Structural modifications are those that change the size of the* list, or otherwise perturb it in such a fashion that iterations in* progress may yield incorrect results.*….*/protected transient int modCount &#61; 0;
只贴前面一部分注释&#xff0c;注释说这个变量来记录ArrayList集合的修改次数&#xff0c;也说明了可能会和迭代器内部的期望值不一致&#xff1b;
另外一个是Itr的变量expectedModCount;
能过上面的代码可以看到在Itr创建时默认定义了 int expectedModCount &#61; modCount;
我们先只看remove的操作:
通过在if条件成立时remove(“3”)操作的断点&#xff0c;我们进入到ArrayList下的remove方法&#xff0c;注意这里并没有进入内部迭代器Itr的remove()方法【这里是产生异常的关键点】
public boolean remove(Object o) {if (o &#61;&#61; null) {for (int index &#61; 0; index
很显然&#xff0c;这里应该正常的走到了fastRemove()方法中&#xff1a;
/** Private remove method that skips bounds checking and does not* return the value removed.*/private void fastRemove(int index) {modCount&#43;&#43;;int numMoved &#61; size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index&#43;1, elementData, index,numMoved);elementData[--size] &#61; null; // clear to let GC do its work}
这里可以看到在fastRemove()方法中通过modCount&#43;&#43; 自增了一次&#xff0c;而此时并没有改变内部迭代器Itr中的expectedModCount 的值&#xff1b;
我们再往下走&#xff0c;此会再迭代到下一个元素&#xff1b;
先会通过hasNext(){return cursor !&#61; size;}来判断是否还有元素&#xff0c;很显然&#xff0c;若删除前面的元素&#xff0c;此处一定会为true&#xff08;注意&#xff1a;若之前删除的是倒数第二个元素&#xff0c;此处的cursor就是最后一个索引值size()-1,而由于已成功删除一个元素&#xff0c;此处的siz也是原size()-1,两者相等&#xff0c;此处会返回false&#xff09;
而在调用next()方面来获取下一个元素时&#xff0c;可以看到在next()方法中先调用了checkForComodification()方法&#xff1a;
final void checkForComodification() {if (modCount !&#61; expectedModCount)throw new ConcurrentModificationException();}
很显然&#xff0c;此处的modCount已经比expectedModCount大1了&#xff0c;肯定不一样&#xff0c;if条件成立&#xff0c;抛出一个ConcurrentModificationException异常。
致此&#xff0c;我们大概理清了为什么在foreach快速遍历中删除元素会崩溃的原因。
总结一下&#xff1a;
1&#xff09;在使用For-Each快速遍历时&#xff0c;ArrayList内部创建了一个内部迭代器iterator&#xff0c;使用的是hasNext和next()方法来判断和取下一个元素。
2&#xff09;ArrayList里还保存了一个变量modCount&#xff0c;用来记录List修改的次数&#xff0c;而iterator保存了一个expectedModCount来表示期望的修改次数&#xff0c;在每个操作前都会判断两者值是否一样&#xff0c;不一样则会抛出异常&#xff1b;
3&#xff09;在foreach循环中调用remove()方法后&#xff0c;会走到fastRemove()方法&#xff0c;该方法不是iterator中的方法&#xff0c;而是ArrayList中的方法&#xff0c;在该方法中modCount&#43;&#43;; 而iterator中的expectedModCount并没有改变&#xff1b;
4&#xff09;再次遍历时&#xff0c;会先调用内部类iteator中的hasNext(),再调用next(),在调用next()方法时&#xff0c;会对modCount和expectedModCount进行比较&#xff0c;此时两者不一致&#xff0c;就抛出了ConcurrentModificationException异常。
而为什么只有在删除倒数第二个元素时程序没有报错呢&#xff1f;
因为在删除倒数第二个位置的元素后&#xff0c;开始遍历最后一个元素时&#xff0c;先会走到内部类iterator的hasNext()方法时&#xff0c;里面返回的是 return cursor !&#61; size; 此时cursor是最后一个索引值&#xff0c;即原size()-1,而由于已经删除了一个元素&#xff0c;该方法内的size也是原size()-1,故 return cursor !&#61; size&#xff1b;会返回false,直接退出for循环&#xff0c;程序便不会报错。
最后&#xff0c;通过源代码的判断&#xff0c;要在循环中删除元素&#xff0c;最好的方式还是直接拿到ArrayList对象下的迭代器list.iterator()&#xff0c;通过源码可以看到&#xff0c;该方法也就是直接把内部的迭代器返回出来
public Iterator iterator() {return new Itr();}
而该迭代器正是在For-Each快速遍历中使用的迭代器Itr。
博客地址&#xff1a;为什么不能在ArrayList的For-Each循环中删除元素
作者&#xff1a;南山伐木
链接&#xff1a;https://www.jianshu.com/p/58c4af5b97ff
來源&#xff1a;简书
简书著作权归作者所有&#xff0c;任何形式的转载都请联系作者获得授权并注明出处。