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

225 阅读3分钟
集合迭代—神秘的倒数第二个元素
引言
学过基础班的同学估计都知道什么叫并发修改异常,即
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
是否相同了,所以就不会再报出并发修改异常了。。