• 迭代器机制及并发修改异常的解决


    迭代器概述

    迭代器原理

    • 迭代器是对集合进行遍历,而每一个集合内部的存储结构都是不同的,所以每一个集合存和取都是不一样,那么就需要在每一个类中定义hasNext()和next()方法,这样做是可以的,但是会让整个集合体系过于臃肿,迭代器是将这样的方法向上抽取出接口(规则),然后在每个类的内部,定义自己迭代方式,这样做的好处有二,第一规定了整个集合体系的遍历方式都是hasNext()和next()方法,第二,代码由底层内部实现,使用者不用管怎么实现的,会用即可

    迭代器用法

    • Java中的Iterator功能比较简单,并且只能单向移动:

      1. 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

      2. 使用next()获得序列中的下一个元素。

      3. 使用hasNext()检查序列中是否还有元素。

      4. 使用remove()将迭代器新返回的元素删除   

    • 代码实例

      import java.util.ArrayList;
      import java.util.Iterator;
      
      //ArrayList存储字符串并遍历
      public class Demo2_generic {
          public static void main(String[] args) {
              ArrayList<String> list = new ArrayList<>();
              list.add("a");
              list.add("b");
              list.add("c");
              list.add("d");
      
              Iterator<String> it = list.iterator();
              while(it.hasNext()) {
                  System.out.println(it.next());
              }
          }
      }

      运行结果:

      a
      b
      c
      d
      

    可以看到,Iterator可以不用管底层数据具体是怎样存储的,都能够通过next()遍历整个List。

    但是,具体是怎么实现的呢?背后机制究竟如何呢?

    迭代器机制

    这里我们来看看Java里AbstractList实现Iterator的源代码:

    public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    protected AbstractList() {}
    
        ......
    
    public Iterator<E> iterator() {
    return new Itr();         //返回一个迭代器
    }
    
    private class Itr implements Iterator<E> {
    
        int cursor = 0;
        int lastRet = -1;
        int expectedModCount = modCount;
    
        public boolean hasNext() {  //实现hasNext方法
            return cursor != size();
        }
    
        public E next() {           //实现next方法
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }
    
        public void remove() {      //实现remove方法
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
    
            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
    
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

    可以看到,实现next()是通过get(cursor),然后cursor++,通过这样实现遍历。

    这部分代码不难看懂,唯一难懂的是remove操作里涉及到的expectedModCount = modCount;

    在网上查到说这是集合迭代中的一种“快速失败”机制,这种机制提供迭代过程中集合的安全性。

    从源代码里可以看到增删操作都会使modCount++,通过和expectedModCount的对比,迭代器可以快速的知道迭代过程中是否存在list.add()类似的操作,存在的话快速失败!

    在上面的例子中加入一条语句:

    package com.zuikc.generic;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    
    //ArrayList存储字符串并遍历
    public class Demo2_generic {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
    
        Iterator<String> it = list.iterator();
        while(it.hasNext()) {
            System.out.println(it.next());
            list.add("s");
        }
    }
    }

    会抛出如下异常

    a
    Exception in thread "main" 
    java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.zuikc.generic.Demo2_generic.main(Demo2_generic.java:17)
    

    ConcurrentModificationException翻译过来是并发修改异常.也就是在迭代器遍历期间不允许其他方法修改元素.

    ConcurrentModificationException解决方法

    这时我们就需要找到一个迭代器自身的方法,在list接口下我们可以查到一个ListIterator方法.如果想在遍历的过程中修改元素,就可以使用此方法.

    ListIterator:系列表迭代器,允许程序员按任一方向遍历列表、迭代期间修改列表,并获得迭代器在列表中的当前位置.

    代码如下:

    package com.zuikc.generic;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.ListIterator;
    
    //ArrayList存储字符串并遍历
    public class Demo2_generic {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
    
        ListIterator<String> it = list.listIterator();
        while(it.hasNext()) {
            System.out.println(it.next());
            it.add("s");
        }
    
        System.out.println(list);
    }
    }

    结果如下:

    a
    b
    c
    d
    [a, s, b, s, c, s, d, s]
  • 相关阅读:
    20160813上午训练记录
    [bzoj 2159]Crash的文明世界
    【娱乐】高端小游戏Manufactoria
    【教程】如何正确的写一个Lemon/Cena的SPJ(special judge)
    [CF]codeforces round#366(div2)滚粗记
    洛谷 [P3265] 装备购买
    POJ 1830 开关问题
    洛谷 [P4035] 球形空间生成器
    BZOJ 2973 入门OJ4798 石头游戏
    洛谷 [P1939] 矩阵加速数列
  • 原文地址:https://www.cnblogs.com/houjx/p/9427066.html
Copyright © 2020-2023  润新知