集合迭代—神秘的倒数第二个元素

集合迭代—神秘的倒数第二个元素

引言

学过基础班的同学估计都知道什么叫并发修改异常,即ConcurrentModificationException,之所以产生这个异常,是因为在迭代器遍历集合的过程中,是不允许集合对元素进行增删的,但却可以用迭代器去增删元素,如果你在迭代器遍历集合的时候,强行让集合对元素进行增删就会产生并发修改异常,比如下面的代码就会产生并发修改异常:
集合迭代—神秘的倒数第二个元素                               


 

这是因为在迭代器遍历集合的时候,让集合对元素进行了增删。
如下代码就不会产生并发修改异常:
集合迭代—神秘的倒数第二个元素

 

这是因为迭代器遍历集合的时候,让迭代器对元素进行了增删。这也是并发修改异常这个类被设计出来的原因,下面是我从API上截取出来的类的介绍
集合迭代—神秘的倒数第二个元素


问题

并发修改异常,这虽然是一个众所周知的知识点,但是问题却来了,当我们如果我们遍历到集合的倒数第二个元素的时候,让集合删除任意一个元素,并不会出现并发修改异常,我们去观察这一下现象,代码如下:
集合迭代—神秘的倒数第二个元素
集合迭代—神秘的倒数第二个元素

 

 

 

为什么会出现这样的问题呢?我们又去API查看此类的时候我们发先API中有如下的解释
集合迭代—神秘的倒数第二个元素

 

并发修改异常这个类,并不能硬性保证是否出现并发修改,但是会尽最大努力,因此他的“不能硬性保证”就表现在了“倒数第二个元素”。

探究

但是仅仅只得到了这样一个结论,我还是不甘心。所以我们一起去看看迭代器的源码,为什么在倒数第二个元素就不能保证发生并法修改呢?为了方便大家理解什么是迭代器,我给大家做了一个迭代器和集合的简易模型。
集合迭代—神秘的倒数第二个元素

 

通过上面的代码我们可以看出,迭代器的实现类是集合的内部类,迭代器其实就是在获取的集合底层数组存储的元素,每next()一次,索引就增加一次,这样就能把集合里面的元素都遍历出来了。
但是JDK中提供的迭代器的源码,并没有如此简单,请大家仔细查看下面的源码,必须明白每一个用红色矩形圈起来的代码的意思。
集合迭代—神秘的倒数第二个元素

 

查看源码之后并且理解之后,我们再来回想我们的并发修改异常产生的原因参照下面代码
集合迭代—神秘的倒数第二个元素

 

当我们遍历到第二个元素,删除完毕“abc2”元素的时候,modCount就自增了一次,接着迭代器调用hasNext()方法判断cursor和size是否相同,结果发现不同,接着调用next()的时候发现expectedModCount和modCount不同,所以就报出了并发修改异常。
现在我们再来看看为什么遍历到倒数第二个元素的时候,删除就不会报出并发修改异常呢?参照如下代码:
集合迭代—神秘的倒数第二个元素

 

当我们遍历到倒数第二个元素,cursor的值是3,删除完毕“abc4”元素的时候,modCount就自增了一次,而size也从4变成了3,接着迭代器调用hasNext()方法判断cursor和size是否相同,结果发现相同都是3,所以返回false,那么while循环就会终止,就不会再继调用next()了。所以就不需要比较expectedModCount和modCount是否相同了,所以就不会再报出并发修改异常了。。