zoukankan      html  css  js  c++  java
  • ArrayList 迭代器学习笔记

    我们先来看一段代码:

    List<String> list = new ArrayList<>();
    list.add("str1");
    list.add("str2");
    list.add("str3");
    for (String s : list) {
        if ("str1".equals(s)) {
            list.remove(s);
        }
    }

    这段代码看起来好像没有什么问题,但是如果我们运行,就会抛出ConcurrentModificationException异常。

    其实这不是特例,每当我们使用迭代器遍历元素时,如果修改了元素内容(添加、删除元素),就会抛出异常,由于 foreach 同样使用的是迭代器,所以也有同样的情况,大家可以自行试一下。我们来通过源码探究一下这个现象的根本原因。

    ArrayList 源码阅读

    下面是 ArrayList 的部分源码,可以明显的看到共有两个remove()方法,一个属于 ArrayList 本身,还有一个属于其内部类 Itr。

    public class ArrayList<E> {
    ​
        void remove() {
            modCount++;  // 继承自AbstractList的属性,保存对其中元素的修改次数,每次增加或删除时加1
            // 具体删除操作代码
            //...
        }
      
        public Iterator<E> iterator() {
            return new Itr();
        }
    ​
        private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            // 在创建迭代器时将当前ArrayList的修改次数赋值给 expectedModCount 保存
            int expectedModCount = modCount;
    ​
            public boolean hasNext() {
                return cursor != size;
            }
    ​
            @SuppressWarnings("unchecked")
            public E next() {
                // 检查当前所在的 ArrayList 的 modCount 是否与创建 Itr 时的值一致,
                // 也就是判断获取了Itr迭代器后 ArrayList 中的元素是否被 Itr 外部的方法改变过。
                checkForComodification();
                // 具体的获取下一个元素的代码
                // ...
            }
    ​
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                // 同 next 中的 checkForComodification 方法
                checkForComodification();
    ​
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    // Itr 内部的删除元素操作,会更新 expectedModCount 值,而外部的则不会
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    ​
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
    }

    ArrayList 类中有一个 modCount 属性,这个属性是继承子AbstractList,其保存了我们对 ArrayList 进行的的操作次数,当我们添加或者删除元素时,modeCount 都会进行对应次数的增加。

    迭代器迭代时外部方法添加或删除元素

    在我们使用 ArrayLis 的 iterator() 方法获取到迭代器进行遍历时,会把 ArrayList 当前状态下的 modCount 赋值给 Itr 类的 expectedModeCount 属性。如果我们在迭代过程中,使用了 ArrayList 的 remove()add()方法,这时 modCount 就会加 1 ,但是迭代器中的expectedModeCount 并没有变化,当我们再使用迭代器的next()方法时,它会调用checkForComodification()方法,即

    final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }

    发现现在的 modCount 已经与 expectedModCount 不一致了,则会抛出ConcurrentModificationException异常。

    迭代器迭代时,使用迭代器的方法添加或删除元素

    但是如果我们使用迭代器提供的remove()方法,由于其有一个操作:expectedModCount = modCount;,会修改expectedModCount 的值,所以就不会存在上述问题。

    总结

    当我们使用迭代器迭代对象的时候,不要使用迭代器之外的方法修改元素,否则会报异常。如果我们要在迭代器迭代时进行修改,可以使用迭代器提供的删除等方法。或者使用其他方法遍历修改。


    注意:

    特殊情况下在迭代器过程中使用 ArrayList 的删除方法不会报异常,就是 只删除倒数第二个元素的时候,代码如下:

    List<String> list = new ArrayList<>();
    list.add("str1");
    list.add("str2");
    list.add("str3");
    for (String s : list) {
        if ("str2".equals(s)) { // 必须只能是倒数第二个元素,这样才不会抛异常
            list.remove(s);
        }
    }

    其原因是迭代器的hasNext()方法:

    public boolean hasNext() {
        return cursor != size;
    }

    在只删除了倒数第二个元素的时候,cursor 会与 size 相等,这样hasNext()方法会返回 false ,结束迭代,也就不会进入 next()方法中,进而执行checkForComodification()检查方法抛出异常。

     

  • 相关阅读:
    暂存未看的网址
    学习springboot+shiro的实际应用demo
    mapper.xml 的配置
    使用idea搭建Spring boot+jsp的简单web项目
    一文读懂SpringCloud与Eureka,Feign,Ribbon,Hystrix,Zuul核心组件间的关系
    .net core mvc 类库读取配置文件
    .net Core 2.0应用程序发布到IIS上注意事项
    Dapper扩展
    C# 请求Https
    js 记录
  • 原文地址:https://www.cnblogs.com/zawier/p/6907971.html
Copyright © 2011-2022 走看看