Java-API简析_java.util.ArrayList<E>类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/131119289
出自【进步*于辰的博客

注:依赖类:ArraysSystem

文章目录

1、概述

继承关系:

  • java.lang.Object
    • java.util.AbstractCollection<E>
      • java.util.AbstractList<E>
        • java.util.ArrayList<E>

所有已实现的接口:
Serializable、Cloneable、Iterable<E>Collection<E>List<E>、RandomAccess

直接已知子类:
AttributeList、RoleList、RoleUnresolvedList


public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

List 接口的大小可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(注:此类大致上等同于 Vector 类,除了此类是不同步的

size()isEmpty()get()set()iterator()listIterator() 操作都以固定时间运行。add() 操作以分摊的固定时间 运行,也就是说,添加 n 个元素需要 O(n) 时间。其他所有操作都以线性时间运行(大体上讲)。与用于 LinkedList 实现的常数因子相比,此实现的常数因子较低。

每个 ArrayList 实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。

在添加大量元素前,应用程序可以使用 ensureCapacity() (见第3.8项)操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

注意,此实现不是同步的 \color{red}{注意,此实现不是同步的} 注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须 保持外部同步(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改)。这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedList() 方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问:

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

此类的 iterator()listIterator() 方法返回的迭代器是快速失败 的:在创建迭代器之后,除非通过迭代器自身的 remove()add() 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

示例:

List<Integer> list = Collections.synchronizedList(new ArrayList<>());
// 或者:
// List<Integer> list = new ArrayList();
Iterator<Integer> iterator = list.iterator();
list.add(2023);
while (iterator.hasNext()) {
	sout iterator.next();
}

测试结果:
在这里插入图片描述
注意,迭代器的快速失败行为无法得到保证。 \color{red}{注意,迭代器的快速失败行为无法得到保证。} 注意,迭代器的快速失败行为无法得到保证。因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug

此类是 Java Collections Framework 的成员。

从以下版本开始:
1.2
另请参见:
Collection<E>、List<E>LinkedList<E>Vector<E>Collections.synchronizedList(List)序列化表格

2、构造方法摘要

1:字段说明

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

// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认容量的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// JVM分配给数组的最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

EMPTY_ELEMENTDATA DEFAULTCAPACITY_EMPTY_ELEMENTDATA 区分开的原因是,以便知道添加第一个元素时要膨胀(扩容)多少。

可能大家不太明白是何意,见第4.2项。

2:属性说明

transient Object[] elementData;// [底层存储结构]
private int size;// 元素个数
protected transient int modCount = 0;// 列表在结构上被修改的次数

属性modCount 补充说明:(父类AbstractList属性)

结构修改是指改变列表的大小,或者以某种方式扰乱列表,从而使进行中的迭代可能产生不正确的结果。

该字段由iteratorlisttiterator方法返回的迭代器和列表迭代器实现使用。如果该字段的值发生意外变化,迭代器(或列表迭代器)将抛出ConcurrentModificationException,以响应next()remove()previous()set()add()操作。这提供了快速故障行为,而不是在迭代期间面对并发修改时的不确定性行为。

子类使用此字段是可选的 \color{red}{子类使用此字段是可选的} 子类使用此字段是可选的。如果子类希望提供快速失败迭代器(和列表迭代器),那么它只需要在其add(int, E)remove(int)(以及它覆盖的导致列表结构修改的任何其他方法)中增加该字段。单个调用add(int, E)remove(int)必须向该字段添加不超过一个,否则迭代器(和列表迭代器)将抛出虚假的 Concurrentmodificationexception。如果实现不希望提供快速失败迭代器,则可以忽略此字段。

2.1 null

构造一个初始容量为 10 的空列表。

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;// 见第4.2项
}

2.2 Collection<? extends E> c

构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。

public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
    	    // 说明调用的是此类的toArray(),返回Object[],故可直接赋值
            elementData = a;
        } else {
        	// 将 a 中所有元素复制到 elementData
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        elementData = EMPTY_ELEMENTDATA;
    }
}

2.3 int initialCapacity

构造一个具有指定初始容量的空列表。

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

3、方法摘要

3.1 boolean add(E o)

将指定的元素追加到此列表的尾部。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);// 尝试扩容,见第4.3项
    elementData[size++] = e;
    return true;
}

3.2 void add(int index, E element)

将指定的元素插入此列表中的指定位置。

public void add(int index, E element) {
    rangeCheckForAdd(index);// 范围检查,见第4.1项

    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1, size - index);// 元素后移
    elementData[index] = element;
    size++;
}

arraycopy()System类的第2.1项。

3.3 boolean addAll(Collection<? extends E> c)

按照指定 Collection 的迭代器所返回的元素顺序,将该 Collection 中的所有元素追加到此列表的尾部。

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();// 见第13项
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);
    
    // 将 c 中的所有元素遍历后追加到当前数组末尾
    System.arraycopy(a, 0, elementData, size, numNew);
    
    size += numNew;
    return numNew != 0;
}

3.4 boolean addAll(int index, Collection<? extends E> c)

从指定的位置开始,将指定 Collection 中的所有元素插入到此列表中。

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);// 元素后移

	// 提取a中所有元素,覆盖所空出位置的所有元素
    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

此方法在业务上相当于整合了第2、3项。

3.5 boolean isEmpty()

判断当前列表是否为空。

public boolean isEmpty() {
    return size == 0;
}

3.6 int indexOf(Object o)

检索指定对象o第一次出现的索引。

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

3.7 int lastIndexOf(Object o)

检索指定对象o最后一次出现的索引。

public int lastIndexOf(Object o) {
    if (o == null) {
        for (int i = size-1; i >= 0; i--)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = size-1; i >= 0; i--)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

3.8 void ensureCapacity(int minCapacity)

(提前)扩容,从而减少后续添加元素时递增式再分配的数量 / 次数。

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        ? 0: DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);// 见第4.4项
    }
}

3.9 E remove(int index)

移除指定索引的元素。

public E remove(int index) {
    rangeCheck(index);// 见第4.7项

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null;

    return oldValue;
}

业务与第4项相同。

关于范围检查: 关于范围检查: 关于范围检查:

诸如此项与第2项此类操作,都必须检查指定索引index是否超出列表范围,如:index > size || index < 0(即第5.1项),从而防止数组下标越界异常。不过,有时不一定会在一开始就进行完全检查,如:index >= size(即第.7项),至于index < 0这种情况则是通过后续逻辑进行排除(当然,第5.1、5.7项是2种适用于2种不同情况的检查,并不是说第5.1项完善于第5.7项)。

一个疑惑: \color{brown}{一个疑惑:} 一个疑惑:

此方法用于移除元素,自然0 <= index < size,故范围检查应是index >= size || index < 0(即结合了第5.1、5.7项)。简言之,第5.1或5.7项都不足以达到需求,可实际上此处仅调用了第5.7项。一开始,我以为此处也是不完全检查。可是,假若index = -1,仅elementData(index)就会抛出 ArrayIndexOutOfBoundsException。可以说,此方法在初始开发时就设定为仅用于底层调用(不对外界开放,自然不需要考虑index < 0这种情况),可实际上此方法由public修饰,这就是我疑惑之处(可能开发者有我不知的考虑)。

3.10 Iterator<E> iterator()

获取迭代器。

public Iterator<E> iterator() {
    return new Itr();// 见第7项
}

3.11 E set(int index, E element)

设置列表元素值。

public E set(int index, E element) {
    rangeCheck(index);

    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

再一个疑惑: 再一个疑惑: 再一个疑惑:(参考第9项)

此处也仅调用了第5.7项。若认定也是“不完全检查”,可elementData[index]处,假若index = -1,同样会抛出 ArrayIndexOutOfBoundsException,而此方法同样由public修饰。

3.12 ListIterator<E> listIterator()

返回子迭代器。

public ListIterator<E> listIterator() {
    return new ListItr(0);// 构造一个游标指向 0 的子迭代器,见第8.2.1项
}

3.13 Object[] toArray()

以正确顺序返回包含此列表中所有元素的数组。

public Object[] toArray() {
    return Arrays.copyOf(elementData, size);
}

3.14 <T> T[] toArray(T[] a)

将此列表中所有元素覆盖指定数组相应索引元素,并返回指定数组。若指定数组的长度大于此列表的长度,则将超出部分的第一个元素置空。

public <T> T[]toArray(T[] a) {
    if (a.length < size)
		// 说明 a 不足以容纳 elementData 的所有元素
		// 复制 elementData 所有元素,构造数组
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    // 复制 elementData 所有元素至 a
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)// ------A
        a[size] = null;
    return a;
}

copyOf()见Arrays类的第2.18项。

从示例中便可知 A 的作用,但我暂不知其意图。

示例:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

Integer[] arr1 = {2021, 2022};
arr1 = list.toArray(arr1);// [1, 2, 3]

Integer[] arr2 = {2021, 2022, 2023};
arr2 = list.toArray(arr2);// [1, 2, 3]

Integer[] arr3 = {2021, 2022, 2023, (int)'#'};
arr3 = list.toArray(arr3);// [1, 2, 3, null]

Integer[] arr4 = {2021, 2022, 2023, (int)'#', (int)'#'};
arr4 = list.toArray(arr4);// [1, 2, 3, null, 35]

补充和扩展: \color{red}{补充和扩展:} 补充和扩展:

  1. 从 从 copyOf() 的源码只知,其底层实现方法也是 \color{grey}{的源码只知,其底层实现方法也是} 的源码只知,其底层实现方法也是arraycopy() ,为什么还要判断 \color{grey}{,为什么还要判断} ,为什么还要判断a.length < size ? ? 因为arraycopy()的限制
    a.length < size时,直接调用arraycopy()将抛出ArrayStoreException;而copyOf()底层是先使用size构造数组,再调用arraycopy(, , , , size),故能正常执行,且复制的是此列表的所有元素。
  2. 为什么不用 \color{grey}{为什么不用} 为什么不用asList()(见 Arrays 类的第2.1项) 构造 构造 构造list ? ?
    为了确保底层调用的是此类的toArray()。(当然,这与asList()返回的是不可变列表无关)

3.15 boolean remove(Object o)

移除指定元素。

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);// 见第5.9项
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);// 移除匹配的第一个元素
                return true;
            }
    }
    return false;
}

3.16 E get(int index)

返回指定索引处的元素。

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

3.17 void forEach(Consumer<? super E> action)

遍历所有元素执行指定的逻辑。

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

示例:使用单循环获取两个列表的交集。

List<Integer> duplicateList = new ArrayList<>();
List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
List<Integer> list2 = Arrays.asList(5, 2, 3, 4);

Map<Integer, Integer> map = new HashMap<>();
list1.forEach(e -> map.put(e, e));
for (Integer e: list2) {
    if (Objects.nonNull(map.get(e)))
        duplicateList.add(e);
}
sout duplicateList;// [2, 3, 4]

3.18 void replaceAll(UnaryOperator operator)

遍历所有元素执行指定的逻辑,并替换旧元素。

public void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        elementData[i] = operator.apply((E) elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}

示例:替换列表中所有指定的敏感字符。

List<String> list1 = Arrays.asList("毛", "爷", "爷");
list1.replaceAll(e-> {
    return e.equals("爷")? "#": e;
});
sout list1;// [毛, #, #]

3.19 boolean removeIf(Predicate<? super E> filter)

移除所有满足指定条件的元素。

// 为了方便大家理解,我将下面示例的数据作为 this
public boolean removeIf(Predicate<? super E> filter) {
   Objects.requireNonNull(filter);

   int removeCount = 0;
   final BitSet removeSet = new BitSet(size);
   final int expectedModCount = modCount;
   final int size = this.size;
   
   for (int i=0; modCount == expectedModCount && i < size; i++) {
       final E element = (E) elementData[i];
       if (filter.test(element)) {
           removeSet.set(i);
           removeCount++;
       }
   }
	// 执行到这里,removeSet保存了 {1, 5, 7} 的“存在状态”,
	// 这就是后续要移除的元素的下标,removeCount是 3

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

   final boolean anyToRemove = removeCount > 0;
   if (anyToRemove) {
       final int newSize = size - removeCount;
	   
	   // 将不满足指定条件的元素前移,从而“移除”了满足指定条件的元素
       for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
           // 巧妙之处
           i = removeSet.nextClearBit(i);
           elementData[j] = elementData[i];
       }
       
       // 元素前移后,最后removeCount个元素已“无用”,置空
       for (int k=newSize; k < size; k++) {
           elementData[k] = null;  // Let gc do its work
       }
       
       this.size = newSize;
       if (modCount != expectedModCount) {
           throw new ConcurrentModificationException();
       }
       modCount++;
   }

   return anyToRemove;
}

大家要想理解此方法的底层逻辑,就必须对BitSet类,也就是“位图”这种数据结构有一个大致的了解。

示例:

List<Integer> list0 = Arrays.asList(10, 0, 30, 40, 50, 0, 70, 0, 90, 100);
List<Integer> list = new ArrayList<>();
list.addAll(list0);

list.removeIf(i -> i == 0);
sout list;// [10, 30, 40, 50, 70, 90, 100]

我们再来看一下list的底层存储结构:

Field f1 = ArrayList.class.getDeclaredField("elementData");
f1.setAccessible(true);
sout Arrays.toString((Object[]) f1.get(list));// [10, 30, 40, 50, 70, 90, 100, null, null, null]

说明:第17~19项

第17~19项的关键之处在于函数式接口与Lambda表达式的联合使用,这必然是多样的,故3个示例是我尽量“贴合”方法业务举出的。

前2个方法的源码仅是一些“有效性”判断,而最后一个方法中包含了一些业务流程,其中使用到了BitSet类,也就是Java对“位图”数据结构的实现,这也是源码中最为巧妙之处。(如果你想要更快的理解源码,建议你先对BitSet类有一个大致的了解,这对你掌握“位图”也大有裨益)

4、方法摘要(不开放)

4.1 private void rangeCheckForAdd(int index)

检查指定索引是否超出范围。若超出,抛出 IndexOutOfBoundsException。

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

可见,此方法是为了防止数组下标越界异常。

4.2 private static int calculateCapacity(Object[] elementData, int minCapacity)

计算容量。

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// new ArrayList()
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

示例1

List<Integer> list = new ArrayList<>();
list.add(1);

Field f1 = ArrayList.class.getDeclaredField("elementData");
f1.setAccessible(true);
sout Arrays.toString((Object[]) f1.get(list));// [1, null, null, null, null, null, null, null, null, null]

示例2

List<Integer> list0 = Arrays.asList(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
List<Integer> list = new ArrayList<>();
list.addAll(list0);

Field f1 = ArrayList.class.getDeclaredField("elementData");
f1.setAccessible(true);
sout Arrays.toString((Object[]) f1.get(list));// [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

这就是为何此类的初始容量为0、默认容量为10的原因,因为示例中add()addAll()都是首次添加。

4.3 private void ensureCapacityInternal(int minCapacity)

扩容。

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

calculateCapacity()见上1项,ensureExplicitCapacity()见下1项。

4.4 private void ensureExplicitCapacity(int minCapacity)

根据指定容量,尝试扩容。

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    if (minCapacity - elementData.length > 0)
        grow(minCapacity);// 见第6项
}

4.5 private static int hugeCapacity(int minCapacity)

检测是否溢出(超出数组最大长度),同时返回 VM 能分配给数组的最大长度。

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

可见,JVMf分配给列表的最大容量是Integer.MAX_VALUE,一般情况是Integer.MAX_VALUE - 8

4.6 private void grow(int minCapacity)

根据指定容量扩容。

在这里插入图片描述

可见,“扩容”一半。(图中 A)

因此,扩容后的长度:若所需长度大于当前长度的1.5倍,则将长度扩容为所需长度;否则,扩容为当前长度的1.5倍。(取整)

hugeCapacity()见上1项;copyOf()Arrays类的第2.16项,即扩容的方法。

4.7 private void rangeCheck(int index)

检查指定索引是否超出列表长度。若超出,抛出 IndexOutOfBoundsException。

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

4.9 private void fastRemove(int index)

移除指定索引的元素。

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

底层逻辑与第3.9项完全相同,只是未做范围检查、以及不会返回所移除的元素。

7、嵌套类 Itr

7.1 预览

迭代器。

private class Itr implements Iterator<E> {
	int cursor;    // 游标。初始值为0,每迭代(next())一次,后移一位
	int lastRet = -1; // 最后(最近)一次变动的元素索引
	int expectedModCount = modCount;
	
	Itr() {}
	...
}

7.2 方法摘要

7.2.1 public boolean hasNext()

判断是否有下一个元素。

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

cursor是游标,每迭代一次,后移一位(+1)。若cursor == size,说明上一次迭代的元素是末尾元素。

7.2.2 public E next()

返回下一个元素。

public E next() {
    checkForComodification();// 见第5项
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();// ------A
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

NoSuchElementException是在未找到元素时抛出,用于在已完全迭代时再次迭代时的异常处理。

ConcurrentModificationException用于迭代器生成后修改列表结构时的异常处理。这是理论描述,而在此源码中,具体什么情形下抛出?
那就需要考虑elementData.lengthsize的含义和大小关系。

  • size是列表组件(元素)的个数,elementData.length是列表容量;
  • 研究它们的关系(涉及列表的扩容机制),见第4.6项,可知,elementData.length始终大于等于size

既然elementData.length始终大于等于size,那么,源码中 A 什么情况下执行?

P S : PS: PS目前我能猜想到的只有一种情况,就是并发条件下,一个线程在调用checkForComodification()后、调用 A 之前,另一个线程对列表结构进行了修改,并且导致了扩容。

7.2.3 public void remove()

移除末尾元素。

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();// 见第5项

    try {
        ArrayList.this.remove(lastRet);// 见第3.9项
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

属性 属性 属性lastRet 到底是做什么的? \color{grey}{到底是做什么的?} 到底是做什么的?
lastRet是最近一次变动的元素的索引,为何lastRet = -1
既然出现在这里,那肯定与此方法的业务相关。
此方法用于移除元素,那我们就先梳理一下元素移除的逻辑。

  1. checkForComodification(),检查在迭代器生成后是否修改过列表结构;
  2. remove(),移除元素;
  3. 定义列表序列为2021, 2022, 2023, 2024, 2025,在移除元素前已迭代了3次,则cursor3lastRet指向2023,为2。那么,下1次迭代的元素就是2024。在移除元素后(调用remove()时的实参是lastRet,故移除的元素是2023),列表序列变为2021, 2022, 2024, 2025。可是,此时cursor3。所以,需要cursor = 2,即cursor = lastRet

这就是元素移除的大致过程。那么,就出现了个问题。假如再次调用此方法,还能顺利移除元素么?
上一次移除元素后,curosr2lastRet2。若再次移除元素,移除的应是2024

那么,开始移除,再走一遍上述流程。省略。。。
结果:列表序列变为2021, 2022, 2025cursor仍为2lastRet也仍为2,移除完成。
然后再迭代,迭代的元素是20252024哪去了?这就是问题所在。

那么,如何解决此问题? \color{grey}{那么,如何解决此问题?} 那么,如何解决此问题?
首先,问题是如何出现的?就是在移除元素后再次移除元素。
因此,只要我们阻止连续移除元素即可(检查发现是连续移除就抛出异常)。
所以,大家就知道为何lastRet = -1了吧。如果没反应过来,再连同如下代码看看:

if (lastRet < 0)
	throw new IllegalStateException();

此检查在此方法和第8.3.3项中都有,第8.3项中的作用也是如此。

补充一点: \color{green}{补充一点:} 补充一点:
为何第8.3.3项中没有lastRet = -1
lastRet的作用是阻止连续移除元素,处理的错误出现在两次连续移除元素后的迭代时。
若是在remove()后调用set(),此时列表序列依旧是2021, 2022, 2024, 2025,正常。再执行set()也没问题。
而若是连续调用set(),由于未改变列表结构,自然也没有问题。因此,不需要lastRet = -1

再补充一点: \color{blue}{再补充一点:} 再补充一点:

if (lastRet < 0)
	throw new IllegalStateException();

此判断的另一个意图:防止异常操作。如:在未迭代的情况下移除(remove(-1))或设置(set(-1, e))元素。

因为,在迭代(next())时,会执行lastRet = i = cursor。故若未迭代,lastRet-1

注意一点: \color{red}{注意一点:} 注意一点:
由于移除元素必定是在迭代后,故无论是否调用过remove()或是否在调用remove()后新生成迭代器,都不会影响迭代结果。因为,lastRet = i = cursorremove(lastRet),大家自行领悟哈。

7.2.4 public void forEachRemaining(Consumer<? super E> consumer)

(业务待明。)
在这里插入图片描述
requireNonNull()Objects类的第2.11项,checkForComodification()见第5项。

关于accept(),可参考博文《[Java]Lambda表达式》的第4.1项。
暂不清楚此处使用函数式接口Consumer<T>(调用此方法)的意图。

图中红框部分的说明见第2项。

后续补充解析。

7.2.5 final void checkForComodification()

检查在迭代器生成后,当前列表是否修改过结构。若修改过,抛出 ConcurrentModificationException

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

modCount是列表在结构上被修改的次数,expectedModCountmodCount的一个“副本”。若两者不相等,说明在迭代器生成后,列表结构修改过,当即抛出 ConcurrentModificationException。且各种操作前都会调用此方法,这就是概述中所述的迭代器“快速失败”的原因。

8、嵌套类 ListItr

8.1 预览

子迭代器。

private class ListItr extends Itr implements ListIterator<E> {
	ListItr(int index) {
	    super();
	    cursor = index;
	}
	
	public boolean hasPrevious() {
	    return cursor != 0;
	}
	
	public int nextIndex() {
	    return cursor;
	}
	
	public int previousIndex() {
	    return cursor - 1;
	}
	...
}

8.2 方法摘要

8.2.1 public boolean hasPrevious()

判断是否有上一个元素。

public boolean hasPrevious() {
    return cursor != 0;
}

此方法与父类的hasNext()对应。

可能大家对这2个方法有点不理解。其实,关键就是 游标 \color{green}{游标} 游标这个概念,此处不必究其理论,其与 for 循环中的i类似(至于i是不是游标,暂未可知)。

8.3.2 public E previous()

返回上一个元素。

public E previous() {
    checkForComodification();// 见第7.2.5项
    int i = cursor - 1;
    if (i < 0)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i;
    return (E) elementData[lastRet = i];
}

此方法与父类的next()对应,源码几乎相同。

8.3.3 public void set(E e)

设置列表元素值。

public void set(Ee) {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();// 见第7.2.5项

    try {
        ArrayList.this.set(lastRet, e);// 见第3.11项
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

8.3.4 public void add(E e)

将指定的元素追加到此列表的尾部。

public void add(E e) {
        checkForComodification();// 见第7.2.5项

        try {
            int i = cursor;
            ArrayList.this.add(i, e);// 见第3.2项
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

为何 为何 为何lastRet = -1?见第7.2.3项。

注意一点: \color{red}{注意一点:} 注意一点:
在未新生成迭代器的情况下,新加入元素不会并入迭代序列。因为,add(i, e)是插入元素,此后元素后移(后移的元素序列是旧迭代序列未迭代部分);然后,cursor = (i = cursor ) + 1。一点提示,大家自行领悟哈。

最后

如果大家需要Java-API文档,我上传了《Java-API文档-包含5/8/11三个版本》。


本文暂缓更新。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进步·于辰

谢谢打赏!!很高兴可以帮到你!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值