ArrayList中的Iterator源码分析(JDK 1.7)

一、阐述

迭代器接口 Iterator

public interface Iterator<E> {
   
   boolean hasNext();

   E next();
   
   void remove();

}

该接口有三个方法:

  • hasNext():判断容器内是否还有更多的元素
  • next():返回迭代器刚越过的元素的引用,返回值是 Object
  • remove():删除迭代器刚越过的元素

二、ArrayList中的Iterator迭代器

因为 ArrayList 继承了 AbstractList 类,因此,ArrayList 中的 Iterator 方法实际上是重写了 AbstractList 中的 Iterator 方法

我们直接来看 AbstracList 类中重写后的该方法

public Iterator<E> iterator() {
    // 实例化 Itr 类的对象
    return new Itr();
}

这个 Itr 是 AbstractList 类中的内部类,实现了 Iterator 接口

// Itr 是 AbstractList 的内部类
private class Itr implements Iterator<E> {
    // 下一个元素的索引位置,默认为 0
    int cursor = 0;

    // 上一个元素的索引位置,默认为 -1
    int lastRet = -1;

    // 期望的修改次数
    int expectedModCount = modCount;

    ......
}

在来看看 Itr 这个内部类中的具体的方法,hasNext() 方法用于判断对于当前位置的元素,是否还有下一个元素。判断的标准是,如果下一个元素的索引位置不等于集合的长度,就有下一个元素,反过来,就没有下一个元素,因为元素的索引位置比元素长度小 1,当下一个元素的索引位置等于集合的长度,那么此时当前元素便是集合的最后一个元素了,肯定不能有下一个元素了

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

三、next 方法

来看 next() 方法,先不看里面的 checkForComodification() 方法,直接看寻找下一个 ArrayList 中的元素的过程

public E next() {
    checkForComodification();
    // 使用变量 i 存放下一个元素的索引位置
    int i = cursor;
    // 如果下一个元素的索引位置大于集合的长度, 抛出异常
    if (i >= size)
        throw new NoSuchElementException();
    // 获取 ArrayList 集合的底层数组
    Object[] elementData = ArrayList.this.elementData;
    // 如果下一个元素的索引位置大于底层数组的长度, 抛出异常
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    // 当前索引的位置向后移动一位
    cursor = i + 1;
    // 
    return (E) elementData[lastRet = i];
}

可以看到,当执行 next 方法的时候,每次获取的都是 当前位置 cursor 的前一个位置 lastRet 上的值,cursor 则是用来判断下一个位置还有没有元素的,好及时停止遍历

举一个例子来看一下 next 的具体流程

我们以上图为例,通过图示进一步的去看是如何执行的


四、remove 方法

在来看一下 remove() 方法

public void remove() {
    // 因为是根据 lastRet 的位置来删除元素的,因此不能是负数,此时会抛出异常
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        // 删除数组在 lastRet 对应的元素,
        ArrayList.this.remove(lastRet);
        // cursor 指向它的前一个位置 lastRet
        cursor = lastRet;
        // 将 lastRet 指向 0 之前的位置
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

public E remove(int index) {
    // 判断当前位置是否大于集合的大小
    rangeCheck(index);

    modCount++;
    // 获取数组在 index 位置对应的元素
    E oldValue = elementData(index);

    // 获取删除元素后需要复制的数组的长度
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 复制旧数组到新数组
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 集合长度减1
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

可以看到,每次删除当前位置 cursor 的前一位 lastRet 对应的元素,即刚刚越过的那个元素,每当删除完毕,当前位置 cursor 回退到被删元素所在的位置,而 lastRet 则退回到位置 -1,等待下一次执行 next() 方法

通过一个例子来执行一下整个遍历的过程

通过图示来看一下整个删除的过程

其中删除 remove() 的过程我其实是省略了一个步骤,应该包括两个步骤,分别是删除元素和移动两个引用的值,比如第一次执行 remove 方法,其实是执行下面两个操作:

  1. 执行 ArrayList.this.remove(lastRet) 方法,删除位置 lastRet 对应的元素 “AAA”
  2. 将 cursor 向前移一个位置,将 lastRet 移到位置 -1

这个时候,cursor 指向的是元素 “BBB”,即被删除元素 “AAA” 的后一个元素,然后开始之后的执行


五、注意点
public class ArrayListTest01 {

    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();

        arrayList.add("AAA");
        arrayList.add("BBB");
        arrayList.add("CCC");

        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            iterator.remove();
            String next = iterator.next();
            System.out.println(next);
        }
    }

}

结果是:

Exception in thread "main" java.lang.IllegalStateException
	at java.util.ArrayList$Itr.remove(ArrayList.java:844)
	at edu.just.failfast.ArrayListTest01.main(ArrayListTest01.java:17)

在使用迭代器遍历的时候,如果一开始没有执行迭代器的 next() 方法,而是直接执行了迭代器的 remove() 方法,此时会报错

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    ...
}

因为在创建迭代器之后,cursor 默认为 0,lastRet 默认为 -1,根据源代码,此时便会报错了,其实想想也是,remove 方法删除的是刚刚“越过”的那个元素,而这个时候啥也没有越过,肯定就会报错了


六、参考

https://www.cnblogs.com/dolphin0520/p/3933551.html
https://www.cnblogs.com/expiator/p/6125141.html
https://blog.csdn.net/u011240877/article/details/52743564

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值