闲聊ArrayList的那些事儿


/   今日科技快讯   /

TikTok在美业务出售事宜尘埃未落,9月8日凌晨,字节跳动发布全员信表示,为感谢员工过去一段时间共同面对全球新冠疫情、宏观环境变化等挑战,公司将向所有符合条件的同事发放奖金:2020年7月1日至8月31日,工作日出勤≥26天的全职员工,将获8月份固定薪资的50%。

/   作者简介   /

本篇文章来自谭嘉俊的投稿,和大家分享了Android中的ArrayList源码的相关分析,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!

谭嘉俊的博客地址:

https://juejin.im/user/2400989124522446

/   概述   /

本文章讲解的内容是Android中的ArrayList源码分析。本文章分析的ArrayList源码是基于Android SDK(版本为28)。

ArrayList是一个大小可以调整的动态数组,它可以允许所有元素(包括null),它提供了增加、删除、修改、查找方法。

ArrayList的数据结构是数组,它会占据一块连续的内存空间,容量是数组的长度(length),增加、删除、查找的时间复杂度是O(n),根据索引访问元素的时间复杂度是O(1)。

ArrayList是线程不安全的,未实现同步,如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改列表(结构修改指的是添加或者删除一个或者多个元素,或者显示调整后备数组的大小的任何操作;仅设置元素的值不是结构修改),则必须在外部进行同步,这通常是通过对自然封装列表的对象进行同步来实现的,如果不存在这样的对象,则应该使用Collections.synchronizedList方法对列表进行包装,最好是在创建时完成此操作,以防止意外地非同步地访问列表,示例代码如下:

List list = Collections.synchronizedList(new ArrayList<>());

ArrayList类的iterator方法和listIterator方法返回的迭代器都是快速失败的,如果在迭代器创建后的任何时候对列表进行了结构上的修改,除了通过迭代器自己的add方法或者remove方法之外的任何方式,迭代器都会抛出ConcurrentModificationException异常,因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在未来某个不确定的时间冒险做出任意的、不确定的行为。

要注意的是,迭代器的快速失败行为不能得到保证,因为一般来说,在存在非同步并发修改时不可能做出任何硬性保证,快速失败的迭代器尽最大努力抛出ConcurrentModificationException异常,我们不应该编写一个依赖于这个异常来保证其正确性的程序,迭代器的快速失败行为应该仅用于检测错误。

ArrayList继承了AbstractList类,并且实现了List接口、RandomAccess接口、Cloneable接口和java.io.Serializable接口,它的UML类图如下所示:

  • AbstractList:该类是个抽象类,并且提供了List接口的骨架实现,以最小化实现由随机访问数据(例如:数组)支持的接口所需的工作,要注意的是,对于顺序访问数据(例如:链表),应该优先使用AbstractSequentialList类。

  • List:有序的集合(也称为序列),用户可以精确地控制每个元素在列表中的位置,通过元素的整数索引(在列表中的位置)访问元素和搜索列表中的元素。

  • RandomAccess:列表实现使用的标记接口,表示它们支持快速(通常是常量时间)随机访问,此接口的主要目的是允许通用算法在应用于随机或顺序访问列表时改变其行为以提供良好的性能。

  • Cloneable:一个类重写Object类的clone方法,并且还要实现该接口(虽然这个接口内并没有定义clone方法),否则就会在调用clone方法时抛出CloneNotSupportedException异常,也就是说,Cloneable只是个合法调用clone方法的标识(maker-interface)。

  • java.io.Serializable:实现该接口的类启用了类的可序列化性,未实现此接口的类将不会序列化或者反序列化其状态,可序列化类的所有子类型本身都是可序列化的,该接口没有字段和方法,只用于标识可序列化的语义。

/   源码分析   /

下面对ArrayList进行源码分析。

字段

ArrayList的字段,源码如下所示:

// ArrayList.java
// 序列化版本号
private static final long serialVersionUID = 8683452581122892189L;

// 默认初始容量为10
private static final int DEFAULT_CAPACITY = 10;

// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 用于默认大小的空实例的共享空数组实例,我们将其与EMPTY_ELEMENTDATA区分开来,以便知道添加第一个元素需要膨胀(inflate)多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储ArrayList元素的数据缓冲区,ArrayList的容量是这个数组缓冲区的长度(length),任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList将在添加第一个元素时扩展为DEFAULT_CAPACITY,设成非私有是为了简化嵌套类访问
transient Object[] elementData;

// ArrayList的大小(包含的元素数量)
private int size;

// 要分配数组的最大大小,一些虚拟机在数组中保留头部信息,尝试分配更大的数组可能会抛出OutOfMemory异常(请求的数组大小超过虚拟机的限制)
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

serialVersionUID是序列化运行时与每个可序列化类关联的一个版本号,该版本号在反序列化期间用于验证序列化对象的发送方和接收方是否为该对象加载了与序列化兼容的类,如果接收方为具有不同于相应发送方的类的serialVersionUID加载了一个类,那么反序列化将导致抛出InvalidClassException异常;一个可序列化的类可以通过声明一个名为serialVersionUID的字段来显示声明它自己的serialVersionUID,这个字段必须是静态且被final修饰,类型为long。

构造函数

ArrayList的构造函数,源码如下所示:

// ArrayList.java
// 构造一个具有指定初始容量的空列表,形式参数initialCapacity是列表的初始容量
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        // 如果initialCapacity大于0,就会创建一个具有初始容量为initialCapacity的Object数组,并且赋值给elementData数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        // 如果initialCapacity等于0,就会将EMPTY_ELEMENTDATA空数组赋值给elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        // 如果initialCapacity小于0,就会抛出IllegalArgumentException异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 构造一个初始容量为10的空列表
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 构造包含指定集合元素的列表,按照集合的迭代器返回元素的顺序,形式参数c是将其元素放置在此列表中的集合的元素
public ArrayList(Collection<? extends E> c) {
    // 返回包含集合(Collection)c的中所有元素的集合,如果此集合保证其迭代器返回元素的顺序,则此方法必须以相同的顺序返回元素
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // 如果elementData的长度(length)不等于0,就会判断下elementData是否为Object数组,因为c.toArray()可能会错误地不返回一个Object数组,可以看下此链接:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-6260652
        if (elementData.getClass() != Object[].class)
            // 如果elementData不是Object数组,就会深拷贝elementData数组,并且返回大小(size)相等的Object数组
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // 如果elementData的长度(length)小于0,就会把EMPTY_ELEMENTDATA空数组赋值给elementData
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

可以看下Arrays的copyOf方法,这个方法可以深拷贝一个数组,源码如下所示:

// Arrays.java
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    // 判断形式参数newType是否为Object数组的Class对象的引用,如果是就创建一个Object数组,如果不是就去反射创建一个Object数组
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    // 调用System.arraycopy方法,将旧数组的值赋值到新数组
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

可以看下System.arraycopy方法,源码如下所示:

// System.java
@FastNative
public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

它是一个原生(native)方法,这个方法可以从指定位置开始,将指定源数组复制目标数组的指定位置。

  • src:源数组

  • srcPos:源数组的起始位置

  • dest:目标数组

  • destPos:目标数组的起始位置

  • length:要复制的数组元素的数目

大小

ArrayList的大小方法,源码如下所示:

// ArrayList.java
// 返回列表中元素的数量
public int size() {
    return size;
}

清空

ArrayList的清空方法,源码如下所示:

// ArrayList.java
// 从列表中删除所有元素,调用这个方法后,列表将为空(empty)
public void clear() {
    // modCount的值加1
    modCount++;

    // 修改elementData数组所有元素的值为null,方便垃圾收集器回收
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    // 修改size的值为0
    size = 0;
}

增加

ArrayList的增加方法,源码如下所示:

// ArrayList.java
// 将指定的元素追加到列表的末尾;形式参数e是要追加到列表的元素;如果成功就返回true,否则返回false
public boolean add(E e) {
    // 调用ensureCapacityInternal方法,并且传入size加1的值,用于检查是否需要增加容量,要注意的是,这个方法会增加modCount的值
    ensureCapacityInternal(size + 1);
    // 在elementData数组末尾追加e元素,也就是修改elementData数组索引为size的元素为e,并且size的值加1
    elementData[size++] = e;
    return true;
}

// 在列表的指定位置插入指定的元素,将元素当前的位置(如果有的话)和后续的元素向右移动(在它们的索引中添加一个);形式参数index是要插入指定元素的索引,形式参数element是要插入的元素
public void add(int index, E element) {
    if (index > size || index < 0)
        // 如果索引(index)超出范围就抛出IndexOutOfBoundsException异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // 调用ensureCapacityInternal,并且传入size加1的值,用于检查是否需要增加容量,要注意的是,这个方法会增加modCount的值
    ensureCapacityInternal(size + 1);
    // 调用System.arraycopy方法,将elementData数组中索引为index及其后面的(size-index)个元素都往后移动一个位置
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 修改elementData索引为index的元素为element
    elementData[index] = element;
    // size的值加1
    size++;
}

// 按照指定集合(Collection)的迭代器返回的顺序,将指定的集合中的所有元素追加到列表的末尾,要注意的是,如果在操作进行时修改了指定的集合,则此操作的行为未定义(这意味着如果指定的集合是这个列表,并且这个列表是非空的,那么这个调用的行为是未定义的);形式参数c为要追加到列表末尾的集合的元素
public boolean addAll(Collection<? extends E> c) {
    // 返回包含集合(Collection)c的中所有元素的集合,如果此集合保证其迭代器返回元素的顺序,则此方法必须以相同的顺序返回元素
    Object[] a = c.toArray();
    // numNew的值是数组a的长度(length)
    int numNew = a.length;
    // 调用ensureCapacityInternal方法,并且传入数组的大小(size)加上要追加到列表末尾的集合的长度(length),要注意的是,这个方法会增加modCount的值
    ensureCapacityInternal(size + numNew);
    // 将要追加到列表末尾的集合复制到elementData中,索引是数组的末尾
    System.arraycopy(a, 0, elementData, size, numNew);
    // 修改size的值为size的值加上numNew的值
    size += numNew;
    // 如果数组a的长度不为0,就返回true,否则返回false
    return numNew != 0;
}

// 从指定位置开始,将指定集合中的所有元素插入到此列表中,将当前的元素(如果有的话)和后续的元素向右移动(增加它们的索引),新元素将按照指定集合的迭代器返回的顺序出现在列表中;形式参数index是要插入指定集合中的第一个元素的位置的索引,形式参数c是要添加到此列表的集合的元素
public boolean addAll(int index, Collection<? extends E> c) {
    if (index > size || index < 0)
        // 如果索引(index)超出范围就抛出IndexOutOfBoundsException异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    Object[] a = c.toArray();
    int numNew = a.length;
    // 调用ensureCapacityInternal方法,并且传入数组的大小(size)加上要追加到列表末尾的集合的长度(length),要注意的是,这个方法会增加modCount的值
    ensureCapacityInternal(size + numNew);

    int numMoved = size - index;

    if (numMoved > 0)
        // 如果需要移动元素,调用System.arraycopy方法,将elementData数组中索引为index及其后面的numMoved个元素都往后移动numNew的值个位置
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    // 调用System.arraycopy方法,将a数组追加到elementData数组末尾
    System.arraycopy(a, 0, elementData, index, numNew);
    // 修改size的值为size的值加上numNew的值
    size += numNew;
    // 如果数组a的长度不为0,就返回true,否则返回false
    return numNew != 0;
}

可以看下增加容量(扩容)相关的方法,例如:ensureCapacity方法、ensureCapacityInternal方法、ensureExplicitCapacity方法、grow方法和hugeCapacity方法,源码如下所示:

// ArrayList.java
// 如果需要的话,可以调用这个方法来增加这个ArrayList实例的容量,以确保它至少可以容纳最小容量参数指定的元素数量,形式参数minCapacity是期望的最小容量
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // 如果elementData数组不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组,minExpand的值就是0
        ? 0
        // 如果elementData数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组,minExpand的值就是DEFAULT_CAPACITY的值,也就是10
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        // 如果形式参数minCapacity的值大于minExpand的值,调用ensureExplicitCapacity方法,并且传入minCapacity的值
        ensureExplicitCapacity(minCapacity);
    }
}

// 得到最小增加容量的值;形式参数minCapacity是最小增加容量的值
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        // 如果elementData数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组,就去取DEFAULT_CAPACITY的值和minCapacity的值的最大值,并且赋值给minCapacity
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    // 调用ensureExplicitCapacity方法,并且传入minCapacity的值
    ensureExplicitCapacity(minCapacity);
}

// 判断是否需要增加容量;形式参数minCapacity是最小增加容量的值
private void ensureExplicitCapacity(int minCapacity) {
    // modCount的值加1
    modCount++;

    // 如果minCapacity的值大于elementData数组的长度(length),就需要增加容量,调用grow方法,并且传入minCapacity的值,要注意的是,可能存在溢出问题
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 增加容量,以确保它至少可以容纳最小容量参数指定的元素数量,形式参数minCapacity是期望的最小容量,要注意的是,可能存在溢出问题
private void grow(int minCapacity) {
    // oldCapacity的值是elementData数组的长度(length)
    int oldCapacity = elementData.length;
    // newCapacity的值是oldCapacity的值加上oldCapacity的值的二分之一(oldCapacity是正数,它的补码就是它的原码,右移1位相当于oldCapacity除以2的1次方),也就是扩大1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        // 如果minCapacity的值大于newCapacity的值,就将minCapacity的值赋值给newCapacity
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 如果newCapacity的值大于MAX_ARRAY_SIZE的值,就调用hugeCapacity方法,并且传入minCapacity的值,返回结果赋值给newCapacity
        newCapacity = hugeCapacity(minCapacity);
    // 通常minCapacity的值会接近size的值
    // 深拷贝elementData数组,长度(length)是newCapacity的值
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
        // 如果minCapacity小于0,就抛出OutOfMemoryError异常
        throw new OutOfMemoryError();
    // 如果minCapacity的值大于MAX_ARRAY_SIZE的值,就返回Integer.MAX_VALUE的值,否则就返回MAX_ARRAY_SIZE的值
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

删除

ArrayList的删除方法,源码如下所示:

// ArrayList.java
// 移除此列表中指定位置的元素,将后面的元素向左移动(从它们的索引中减去1),形式参数index是要删除的元素的索引
public E remove(int index) {
    if (index >= size)
        // 如果索引(index)超出范围就抛出IndexOutOfBoundsException异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // modCount的值加1
    modCount++;
    // oldValue为要删除的元素
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 调用System.arraycopy方法,将elementData数组中索引为index的后面的numMoved个元素往前移动一个位置
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 修改elementData数组中索引为(size-1)的元素(最后一个元素)为null,方便垃圾收集器回收
    elementData[--size] = null;

    // 返回要删除的元素的值
    return oldValue;
}

// 如果指定元素出现,则从列表中删除第一个出现的元素(索引最低的元素),并且返回true;如果列表中不包含该元素,则不会对其进行修改,并且返回false
public boolean remove(Object o) {
    if (o == null) {
        // 如果o是null,就会遍历elementData数组,查找数组中是null的元素,如果找到,就调用fastRemove方法,传入索引(index),成功移除后返回true
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        // 如果o不是null,就会遍历elementData数组,通过equals方法判断是否值是否相等,如果找到,就调用fastRemove方法,传入索引(index),成功移除后返回true
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    // 如果列表中不包含该元素,就返回false
    return false;
}

// 跳过边界检查且不返回已删除值的私有移除方法;形式参数index是要移除的元素的索引
private void fastRemove(int index) {
    // modeCount的值加1
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        // 如果需要移动元素,调用System.arraycopy方法,将elementData数组中索引为index的后面的numMoved个元素往前移动一个位置
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 修改elementData数组中索引为(size-1)的元素(最后一个元素)为null,方便垃圾收集器回收
    elementData[--size] = null;
}

// 从此列表中移除指定集合中包含的所有元素;形式参数c是要从此列表中删除的集合的元素
public boolean removeAll(Collection<?> c) {
    // 检查c是否为null
    Objects.requireNonNull(c);
    // 如果c不为null,调用batchRemove方法,并且传入c和false
    return batchRemove(c, false);
}

// 仅保留此列表中包含在指定集合中的元素,换句话说就是从该列表中删除指定集合中不包含的所有元素;形式参数c是要保留在此列表中的集合的元素
public boolean retainAll(Collection<?> c) {
    // 检查c是否为null
    Objects.requireNonNull(c);
    // 如果c不为null,调用batchRemove方法,并且传入c和true
    return batchRemove(c, true);
}

// 批量删除;形式参数c是要处理的集合的元素,形式参数complement是判断是否需要保留
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        for (; r < size; r++)
        if (c.contains(elementData[r]) == complement)
            // 如果c包含elementData数组中的某些元素,并且complement是true,也就是retainAll方法调用的时候,就把符合条件的元素按顺序赋值到elementData数组;如果c不包含elementData数组中的某些元素,并且complement是false,也就是removeAll方法调用的时候,就把符合条件的元素按顺序赋值到elementData数组
            elementData[w++] = elementData[r];
    } finally {
        if (r != size) {
            // 如果r不等于size,证明抛出异常,为了保持和AbstractCollection的行为兼容性,就会将出现异常处的后面的数据全部复制到elementData数组中
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            // 将(w+size-r)的值赋值给w
            w += size - r;
         }
         if (w != size) {
             // 将elementData数组索引为w的后面的元素
             for (int i = w; i < size; i++)
                 // 修改elementData数组中对应索引的元素的值为null,方便垃圾收集器回收
                 elementData[i] = null;
                modCount += size - w;
             size = w;
             modified = true;
         }
    }
    return modified;
}

修改

ArrayList的修改方法,源码如下所示:

// ArrayList.java
// 将列表中指定位置的元素替换为指定元素;形式参数index是要替换的元素的索引,形式参数element是需要存储在指定位置的元素;返回被替换的元素的值
public E set(int index, E element) {
    if (index >= size)
        // 如果索引(index)超出范围就抛出IndexOutOfBoundsException异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // 该索引以前的元素的值
    E oldValue = (E) elementData[index];
    // 将该索引的元素的值替换为指定元素
    elementData[index] = element;
    // 返回被替换的元素的值
    return oldValue;
}

查找

ArrayList的查找方法,源码如下所示:

// ArrayList.java
// 如果列表中包含指定元素,就返回true,否则返回false;形式参数o是需要查找的元素
public boolean contains(Object o) {
    // 调用indexOf方法,如果大于或等于0,就返回true,否则返回false
    return indexOf(o) >= 0;
}

// 如果列表中包含指定元素,就返回该元素的第一个匹配项的索引(索引最低的元素);如果列表中不包含该元素,就返回-1;形式参数o是需要查找元素
public int indexOf(Object o) {
    if (o == null) {
        // 如果o是null,就会遍历elementData数组,查找数组中是null的元素,如果找到,就返回对应的索引
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        // 如果o不是null,就会遍历elementData数组,通过equals方法判断是否值是否相等,如果找到,就调用返回对应的索引
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    // 如果列表中不包含该元素,就返回-1
    return -1;
}

// 如果列表中包含指定元素,就返回该元素的最后一次出现匹配项的索引(索引最低的元素);如果列表中不包含该元素,就返回-1;形式参数o是需要查找元素
public int lastIndexOf(Object o) {
    if (o == null) {
        // 如果o是null,就会反向遍历elementData数组,查找数组中是null的元素,如果找到,就返回对应的索引
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        // 如果o不是null,就会反向遍历elementData数组,通过equals方法判断是否值是否相等,如果找到,就调用返回对应的索引
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

// 返回列表中指定位置的元素;形式参数index是要返回元素的索引
public E get(int index) {
    if (index >= size)
        // 如果索引(index)超出范围就抛出IndexOutOfBoundsException异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

    // 返回该索引对应的元素
    return (E) elementData[index];
}

迭代器

ArrayList的迭代器的源码如下所示:

// ArrayList.java
// 以适当的顺序在列表中的元素上返回迭代器
public Iterator<E> iterator() {
    // 创建Itr对象
    return new Itr();
}

// AbstractList.Itr的优化版本
private class Itr implements Iterator<E> {
    // Android更改:添加limit字段来检测迭代结束,这个limit的值创建迭代器时列表的大小。添加和删除元素无论如何都会使迭代无效(并导致next方法抛出),因此保存这个值保证hasNext方法的值保持稳定,并且当元素从列表中添加和删除时,不会在true和false之间翻动
    protected int limit = ArrayList.this.size;

    // 要返回的下一个元素的索引
    int cursor;
    // 最后返回元素的索引,默认值是-1
    int lastRet = -1;
    // 用于判断列表是否在结构上修改过
    int expectedModCount = modCount;

    public boolean hasNext() {
        // 如果cusor的值小于limit的值,就返回true,否则返回false
        return cursor < limit;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (modCount != expectedModCount)
            // 如果列表在结构上修改过,就抛出ConcurrentModificationException异常
            throw new ConcurrentModificationException();
        int i = cursor;
        if (i >= limit)
            // 如果i的值大于limit的值,也就是超过数组的大小(size),就抛出NoSuchElementException异常
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            // 如果列表在结构上修改过,就抛出ConcurrentModificationException异常
            throw new ConcurrentModificationException();
        // 游标(cursor)的值修改为(i+1)
        cursor = i + 1;
        // 返回元素的值,并且设置最后返回元素的索引的值为i
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            // 如果lastRet的值小于0,就抛出IllegalStateException异常
            throw new IllegalStateException();
        if (modCount != expectedModCount)
            // 如果列表在结构上修改过,就抛出ConcurrentModificationException异常
            throw new ConcurrentModificationException();

        try {
            // 调用ArrayList的remove方法,并且传入要删除的索引,该索引为lastRet的值
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            // 修改lastRet的值为-1,防止重复删除
            lastRet = -1;
            // 更新判断列表是否在结构上修改过的标记
            expectedModCount = modCount;
            // limit的值减1
            limit--;
        } catch (IndexOutOfBoundsException ex) {
            // 如果列表在结构上修改过,就抛出ConcurrentModificationException异常
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            // 如果i的值大于列表的大小,就返回
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            // 如果列表在结构上修改过,就抛出ConcurrentModificationException异常
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // 在迭代结束时更新一次以减少堆写流量
        cursor = i;
        lastRet = i - 1;

        if (modCount != expectedModCount)
            // 如果列表在结构上修改过,就抛出ConcurrentModificationException异常
            throw new ConcurrentModificationException();
    }
}

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

PermissionX重磅更新,支持自定义权限提醒对话框

使用MD风格,让你的项目更好看

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值