目录
  1. 1. 深入理解Java集合中的Iterator
    1. 1.1. 涉及知识
      1. 1.1.1. ConcurrentModificationException
      2. 1.1.2. fail-fast
      3. 1.1.3. modCount
      4. 1.1.4. expectedModCount
      5. 1.1.5. 迭代器为什么不报错?
    2. 1.2. 总结:
深入理解Java集合中的Iterator

深入理解Java集合中的Iterator

总结自掘金的一篇文章:深入理解Java集合中的Iterator

首先上一段面试题代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
List list = new ArrayList();
list.add("1");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
for (Object o : list) {
if ("6".equals(o))
list.remove(o);
}
System.out.println(list);
}

问:这段代码会不会报错呢?

这段代码看起来挺平常的,我是没看出来啥问题(还是菜)。

但是我们首先要知道的点:

  • foreach 遍历底层是用迭代器来实现的
  • 如果用迭代器遍历集合元素时,如果要删除或修改集合中元素,就必须使用迭代器的方法,不能用集合自身的方法

image-20200208124552431

所以根据上面的知识点,这段代码应该是会报错的,但是一运行:

image-20200208123029032

没一点毛病,换个数试试?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
List list = new ArrayList();
list.add("1");
list.add("3");
list.add("4");
list.add("5");
list.add("6");
list.add("7");
for (Object o : list) {
if ("3".equals(o)) //仅仅修改了 equals前面的值 6 -> 3
list.remove(o);
}
System.out.println(list);
}

再次运行:

image-20200208123246605

哦豁,就换了个数程序又报错了

既然是根据那篇文章总结,我们就直接了当的概况一下,总结一下大佬们的debug处理方式:

  • 根据现有知识寻找可能出错的点
  • 当出现 bug 时,先从异常入手,分析这个异常是如何发生的(一看官方文档二看源码)
  • 知道了异常的大概发生场景后,看控制台报错的行数,找到具体异常位置
  • 接着就是一层层的往里深入,打断点不断调试,解决问题

涉及知识

ConcurrentModificationException

  • 这个异常会在一个对象被做了不合法的并发修改时抛出(单线程下也会出现)

fail-fast

  • 集合中通常会自带一个 fail-fast 的迭代器
  • 所谓 fail-fast 就是检测到有异常越快抛出异常结束越好,避免了未知的隐患

image-20200208125032417

上图就是在 ArrayList 类中的迭代器,控制台的报错信息指向了里面 next 方法的 checkForComodification() 方法

image-20200208125412653

这个方法也很简单,就是验证了 modeCount 和 expectedModCount 是否一致,不一致则异常

modCount

  • modCount是定义在AbstractList(ArrayList的父类)里面的一个属性。
  • 这个字段用来记录 list 被结构性操作的次数,就是对 list 容量有影响或者迭代过程中会导致错误的操作。
  • 如果想要实现 fail-fast 那就要在所有结构性操作的方法内部做 modCount++ 的操作
  • 如果不想实现 fail-fast 就不需要,ArrayList 里的 add 方法就实现了 modCount++ 操作

expectedModCount

  • 这个值在上面已经出现过了,在 ArrayList 的迭代器中会将 expectedModCount 的值初始化为 modCount 的值

image-20200208135713681

也就是说,在正常情况下,expectedModCount 和 modCount 的值是一致的,但是为什么会抛出不一致的异常呢?

打两个断点开始 debug :

image-20200208140304016

因为刚开始 list 的 size 为 6 ,而 foreach 底层就是迭代器嘛,他会先判断 hasNext() , cursor 是当前迭代器的游标,游标没有到 size 大小就会返回 true ,继续执行 next 方法,而 next 方法刚上来第一步就是检查一致性,因为我们初始化后没有再在结构性上动,所以一直保持一致,当我们迭代到 6 时,执行 remove 操作

image-20200208140904523

image-20200208140920049

上来就是修改 modCount++ ,所以这是 expectedModCount 为 6 ,而 modCount 为 7。迭代器继续迭代,cursor != size,进入 next 方法,上来检查就不一致所以报错。

迭代器为什么不报错?

image-20200208141448037

这说的很清楚了,就是迭代器会帮我们更新 expectModCount 的值,来使它与 modCount 保持一致,所以我们以后都要记得用迭代器的方法,才能保证不出错

至于上面为什么有时候报错,有时候就不报错,其实已经很简单了,跟着流程走一遍自然会发现。

总结:

迭代器的 next 方法会检查一致性,而自己集合的结构性操作只 modCount++,而不更新 expectModCount,所以会报错。

文章作者: Archiver
文章链接: https://www.kaiming66.com/2020/02/08/Java/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E9%9B%86%E5%90%88%E4%B8%AD%E7%9A%84Iterator/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Archiver`s Blog
打赏
  • 微信
  • 支付寶

评论