目录
2.3.2. ArrayList(int initialCapacity)
2.3.3. ArrayList(Collection c)
2.4.2. add(int index, E element)
2.4.4. addAll(int index, Collection c)
2.5.3. removeRange(int fromIndex, int toIndex)
2.5.4. removeAll(Collection c)
ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。由于Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。

从类图中我们可以看出,ArrayList实现了4个接口和继承了1个抽象类;4个接口分别为:
List接口,主要提供数组的添加、删除、修改、迭代遍历等操作。
Cloneable接口,表示ArrayList支持克隆。
RandomAccess接口,表示ArrayList支持 快速的随机访问。
Serializable接口,表示ArrayList支持序列化的功能。
继承的抽象类为 AbstractList,主要提供的是迭代遍历相关的操作;如果我们单纯看 List --> AbstractList --> ArrayList 这一实现/继承关系来看的,是不是有一种模板方法模式的感觉呢? List接口定义了操作行为,AbstractList抽象类提供了可重用的算法骨架;而最终的 ArrayList 实现类根据自己的情况自定义实现接口。
注意:实际上 ArrayList 大量重写了 AbstractList 抽象类提供的方法实现;所以 AbstractList 对 ArrayList来说意义不大,但 AbstractList 的其他很多子类享受到了这个福利。
- public class ArrayList
extends AbstractList - implements List
, RandomAccess, Cloneable, java.io.Serializable - {
- // ....
- }
在进行源码解析前,我们先预览一遍 ArrayList 中的所有方法吧,并且挑选比较核心的方法进行讲解。
- /****************** ArrayList中的构造函数 ***************/
- // 默认构造函数
- ArrayList()
-
- // capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
- ArrayList(int capacity)
-
- // 创建一个包含collection的ArrayList
- ArrayList(Collection extends E> collection)
-
- /****************** ArrayList中的API ********************/
- // Collection中定义的API
- boolean add(E object)
- boolean addAll(Collection extends E> collection)
- void clear()
- boolean contains(Object object)
- boolean containsAll(Collection> collection)
- boolean equals(Object object)
- int hashCode()
- boolean isEmpty()
- Iterator
iterator() - boolean remove(Object object)
- boolean removeAll(Collection> collection)
- boolean retainAll(Collection> collection)
- int size()
T[] toArray(T[] array) - Object[] toArray()
-
- // AbstractCollection中定义的API
- void add(int location, E object)
- boolean addAll(int location, Collection extends E> collection)
- E get(int location)
- int indexOf(Object object)
- int lastIndexOf(Object object)
- ListIterator
listIterator(int location) - ListIterator
listIterator() - E remove(int location)
- E set(int location, E object)
- List
subList(int start, int end) -
- // ArrayList新增的API
- Object clone()
- void ensureCapacity(int minimumCapacity)
- void trimToSize()
- void removeRange(int fromIndex, int toIndex)
ArrayList的属性很少,只有2个属性:elementData 和 size;其中 elementData 代表的是存放元素的数组;而 size 代表的是 elementData 数组中元素的数量。比如下图所示: elementData数组的长度是9,但是只存放了5个元素,这时候 size属性就是5。

我们在使用 ArrayList 的时候会经常调用其 size() 方法,返回的就是 elementData 数组中已使用的元素的数量,也即是当前的 5;并且当我们新添加元素的时候,size所指代的也恰巧是新元素的下标(数组下标从0开始)。
- /**
- * 存放元素的数组
- * 在新增元素的时候,如果遇到数组不够会创建新的数组,并且将原数组的元素拷贝到新数组;最后将该变量指向新数组
- */
- transient Object[] elementData;
-
- /**
- * 数组的大小
- * size代表的是已使用elementData的元素的数量,也即是size()方法的大小
- */
- private int size;
ArrayList一共有3个构造方法,分别如下所示。
无参构造方法,平时使用的时候,如果你的IDEA有安装 sonarLint 扫描,会被提示:创建集合的时候要指定初始化容量哦!
- private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
-
- public ArrayList() {
- // 实际创建的时候是空数组,在首次添加元素的时候,才会初始化容量为10的数组
- this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
- }
无参构造方法使用了 DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象,查看源码可知其定义:private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} 是一个空数组。
经常有朋友会有疑问:“我之前学习的时候被灌输的是----当未指定初始化大小的时候,ArrayList默认的大小是10呀!”
这句话一定程度上来说是没问题的,只是缺失了一些补充描述:当未指定初始化大小的时候,ArrayList先是初始化为一个空数组;但在首次添加元素时,ArrayList才会初始化为一个容量为10的数组。这么做的原因是节省内存,一些场景可能只是单纯的定义了数组并没有真实的使用,直接初始化容量为10的数组会造成不必要的浪费。
细心的朋友可能会问:“既然都是空的数组,那为什么不直接使用 EMPTY_ELEMENTDATA 空数组;而是要重新定义一个新的空数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA呢?”
这个问题设计者有其特殊的考虑,后面在介绍数组扩容的时候会详细介绍两者的不同;这里先简单讲一点,DEFAULTCAPACITY_EMPTY_ELEMENTDATA的首次扩容是 10, EMPTY_ELEMENTDATA的首次扩容是按照1.5倍扩容,从0开始;两者的起点不同。
ArrayList(int initialCapacity)构造方法,根据传入的 initialCapacity 初始化容量来创建 elementData数组。如果我们在使用 ArrayList 的时候,预先知道元素的数量,应该尽可能的使用该构造方法,这样可避免数据扩容,从而提升性能,同时也是合理的利用内存空间。
- private static final Object[] EMPTY_ELEMENTDATA = {};
-
- public ArrayList(int initialCapacity) {
- if (initialCapacity > 0) {
- // 初始化容量大于0,创建Object数组
- this.elementData = new Object[initialCapacity];
- } else if (initialCapacity == 0) {
- // 初始化容量为 0 时,使用 EMPTY_ELEMENTDATA 对象
- this.elementData = EMPTY_ELEMENTDATA;
- } else {
- // 参数异常
- throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
- }
- }
这里需要留意的是:当初始化容量 initialCapcity 为 0 的时候,使用了一个 EMPTY_ELEMENTDATA对象,查看源码可知其定义: private static final Object[] EMPTY_ELEMENTDATA = {} 是一个空数组。在添加元素的时候,会进行扩容创建需要的数组。
ArrayList(Collection extends E> c) 构造方法,使用传入的集合 c 作为 ArrayList的 elementData,当 elementData 数组不空的时候,有一个类型转换的过程,因为 elementData是 Object[]类型的数组。
- public ArrayList(Collection extends E> c) {
- // element指向c.toArray(),toArray()方法会创建一个新的数组
- elementData = c.toArray();
-
- // 数组不为空
- if ((size = elementData.length) != 0) {
- // 如果集合元素的类型不是Object.class类型,则拷贝成Object.class类型
- if (elementData.getClass() != Object[].class) {
- elementData = Arrays.copyOf(elementData, size, Object[].class);
- }
- } else {
- // 数组为空,使用 EMPTY_ELEMENTDATA 对象
- this.elementData = EMPTY_ELEMENTDATA;
- }
- }
新增元素的方法主要有如下4个,分别为:
public boolean add(E e) 在数组后面顺序新增一个元素。
public void add(int index, E element) 在指定 index 位置添加元素。
public boolean addAll(Collection extends E> c) 在数组后面顺序添加多个元素。
public boolean addAll(int index, Collection extends E> c) 在指定 index位置,添加多个元素。
继承于 AbstractList 抽象类,在数组后面顺序添加单个元素。
- public boolean add(E e) {
- // size是当前数组的元素个数,+1代表新增一个元素
- ensureCapacityInternal(size + 1);
- // 这个操作相当于 element[size] = e, size++
- elementData[size++] = e;
- // 返回添加成功
- return true;
- }
方法只有3句代码,看起来比较简单;主要关注一下 ensureCapacityInternal 方法做了什么事情;经过上面的介绍我们知道,ArrayList的底层是使用数组实现,并且其特性是支持动态扩容的;那么当我们新增元素的时候应该需要考虑:现有的数组容量是否支持新增元素,如果容量不满足,则要考虑扩容操作。
我们来看看JDK的设计者是怎么做的:
STEP-1,首先是 ensureCapacityInternal(int minCapacity) 方法,该方法主要功能:确保数组内部容量足以满足本次添加元素操作。方法实现上首先是调用了 calculateCapacity(Object[] elementData, int minCapacity) 方法,计算满足本次新增操作所需的容量,然后调用 ensureExplicitCapacity(int minCapacity) 方法,保证容量足够。
- /**
- * 确保数组容量足够添加元素
- *
- * @param minCapacity 最小容量
- */
- private void ensureCapacityInternal(int minCapacity) {
- // 计算容量
- int capacity = calculateCapacity(elementData, minCapacity);
- // 确保容量足够 -- 如不足够则触发扩容
- ensureExplicitCapacity(capacity);
- }
STEP-2,然后我们来看看 calculateCapacity(Object[] elementData, int minCapacity) 方法是如何计算本次新增操作所需的容量的。
如果 elementData 是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA——没有指定初始化容量时;则会判断是最小容量 minCapacity 和 DEFAULT_CAPACITY 的大小,取大值。
- /**
- * 计算容量
- * @param elementData 元素数组
- * @param minCapacity 最小容量
- * @return int
- */
- private static int calculateCapacity(Object[] elementData, int minCapacity) {
- // 如果是DEFAULT_CAPACITY,那么就会从10开始
- if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
- // DEFAULT_CAPACITY 定义:private static final int DEFAULT_CAPACITY = 10;
- return Math.max(DEFAULT_CAPACITY, minCapacity);
- }
- // 如果不是则直接返回需要的最小容量
- return minCapacity;
- }
STEP-3,ensureExplicitCapacity如何确保数组容量足够本次新增操作的? 由代码可以看出,当所需的最小容量minCapacity大于 elementData.length 数组长度的时候,则触发 grow()执行扩容。
- /**
- * 确保数组剩余容量足够
- *
- * @param minCapacity 最小容量
- */
- private void ensureExplicitCapacity(int minCapacity) {
- // 在父类 AbstractList中定义了用以记录数组修改的次数(注意是次数)
- modCount++;
-
- // minCapacity代表的是本次(新增)操作所需要的最小容量,elementData.length代表的是当前元素数组的长度
- // 可能写成 minCapacity > elementData.length 更好理解
- if (minCapacity - elementData.length > 0) {
- // 扩容可指定最小的容量(满足当前需求)
- grow(minCapacity);
- }
- }
STEP-4,看看grow是怎么扩容的
- /**
- * 扩容
- * 旧容量经过运算扩展为1.5后与最小容量minCapacity进行比较
- * 如果大于则采用旧容量扩展1.5倍后的大小,否则采用最小容量minCapacity
- * @param minCapacity 新增操作所需最小容量
- */
- private void grow(int minCapacity) {
- // 旧容量大小 elementData 数组的长度
- int oldCapacity = elementData.length;
- // 新容量大小 == 旧 + 旧的位运算(向右移动1位),大致上是原大小的1.5倍
- int newCapacity = oldCapacity + (oldCapacity >> 1);
- // 如果计算出来的新容量小于指定的最小容量,则创建指定的最小容量大小的新数组
- // 可理解为 newCapacity < minCapacity,源码中写成了减法的形式。
- if (newCapacity - minCapacity < 0) {
- newCapacity = minCapacity;
- }
- // 如果新容量大于最大值(会出现内存不足的情况)
- // private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
- if (newCapacity - MAX_ARRAY_SIZE > 0) {
- // hugeCapicaty方法用以当 newCapacity 超过指定范围的时候,确保创建的数组不会溢出
- newCapacity = hugeCapacity(minCapacity);
- }
- // 数组拷贝,将原有的数据搬到新的数组上
- elementData = Arrays.copyOf(elementData, newCapacity);
- }
经过一轮讲解,我们大体上知道ArrayList是如何新增元素的,首先计算出新增元素所需要的最小容量,然后判断当前 elementData 数组是否支持本次新增操作(容量是否足够),当容量不足的时候则触发扩容机制,将原有数组的元素拷贝到新数组上,然后往后追加新的元素。
public void add(int index, E element) 方法继承于 AbstractList抽象类,在指定 index 位置新增元素 element。
- /**
- * 在指定index位置添加元素element
- * @param index
- * @param element
- */
- public void add(int index, E element) {
- // 参数合法性校验
- rangeCheckForAdd(index);
- // 确保容量可用----如果不可用会触发扩容机制
- ensureCapacityInternal(size + 1); // Increments modCount!!
- // 数组index位置后的元素都往后移1位
- System.arraycopy(elementData, index, elementData, index + 1, size - index);
- // 指定位置添加element元素
- elementData[index] = element;
- // size + 1
- size++;
- }
-
- /**
- * 参数合法性校验
- * index > size 不允许,只能是对已存在的index进行添加
- */
- private void rangeCheckForAdd(int index) {
- if (index > size || index < 0) {
- throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
- }
- }
方法比较简单,首先是参数的合法性检查,然后就是上面介绍的 ensureCapacityInternal 方法,用以确保数组容量满足本次新增操作;需要额外讲解的是 System.arraycopy() 方法。
System.arraycopy(Object src, int scrPos, Object desc, int destPos, int length) 方法,用于将指定源数组中的元素从指定位置到目标数组的指定位置。
src:源数组
srcPos:源数组复制的起始位置
desc:目标数组
descPos:目标数组填充数据的起始位置
length:要复制源数组的元素数量
例如:
- // 原数组:int[] arr1 = {1,2,3,4,5,6,7,8,9,0};
- // 目标数组:int[] arr2 = new int[4];
- // 操作:将原数组第二个位置以后的4个数据copy到目标数组
- System.arrayCopy(arr1, 1, arr2, 0, 4);
public boolean addAll(Collection extends E> c) 继承于 AbstractCollection 抽象类,在数组后面 顺序添加多个元素。当我们确定会添加多个元素的时候,建议使用该方法而不是单个元素逐个的添加,避免可能多次扩容造成的性能下降。
- /**
- * 在数组后面顺序添加多个元素
- * @param c 集合c
- * @return boolean
- */
- public boolean addAll(Collection extends E> c) {
- Object[] a = c.toArray();
- int numNew = a.length;
- // 确保数组容量足够本次添加元素操作,和添加单个元素的一样;只不过size不再是 + 1 而是 + c.length
- ensureCapacityInternal(size + numNew); // Increments modCount
- // 拷贝数组
- System.arraycopy(a, 0, elementData, size, numNew);
- size += numNew;
- return numNew != 0;
- }
和 add(E e) 很类似,只不过是将添加1个元素变成了添加多个元素罢了。
public boolean addAll(int index, Collection extends E> c) 方法继承于 AbstractList 抽象类,用以在指定 index 位置添加多个元素。
- /**
- * 在指定index位置添加多个元素c
- * @param index
- * @param c
- * @return boolean
- */
- public boolean addAll(int index, Collection extends E> c) {
- // index 参数合法性检查
- rangeCheckForAdd(index);
-
- Object[] a = c.toArray();
- int numNew = a.length;
- // 确保数据容量满足本次添加元素操作 ---- 不满足则扩容
- ensureCapacityInternal(size + numNew); // Increments modCount
- // 原有index后的元素往后移动,移动numNew个位置
- int numMoved = size - index;
- if (numMoved > 0)
- System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
- // 空闲出来的位置,填充需要添加的元素
- System.arraycopy(a, 0, elementData, index, numNew);
- size += numNew;
- return numNew != 0;
- }
首先还是先进行容量可达操作,让数组的容量满足本次的扩容操作;其次,因为需要在 index 位置新增多个元素,则原来在 index 位置后的的元素都应该往后顺延 c.length 个位置。
删除元素的方法主要有4个,分别为如下所示:
public E remove(int index) 移除指定位置的元素,并返回该位置的原元素。
public boolean remove(Object o) 移除首个为 o 的元素,并返回是否移除成功。
protected void removeRange(int fromIndex, int toIndex) 批量移除 [fromIndex, toIndex)内的多个元素,这里需要特别注意: toIndex不包括的噢。
public boolean removeAll(Collection> c) 批量移除指定的多个元素。
public E remove(int index) 继承于 AbstractList 抽象类,删除指定位置的元素,并返回该位置上的原有元素。
- /**
- * 删除指定位置index的元素,并返回该元素
- * @param index
- * @return E
- */
- public E remove(int index) {
- // index 合法性校验,不合法则抛出相关异常
- rangeCheck(index);
- // 修改数组的次数 + 1
- modCount++;
- // 获取index下标对应的value,elementData方法其实就是 return (E) elementData[index]
- E oldValue = elementData(index);
-
- // 每删除一个元素,都需要对原有的数组进行移动,所以从这里也可以看出ArrayList并不是特别适合于删除操作比较多的场景
- // 需要移动的元素的数量
- int numMoved = size - index - 1;
- if (numMoved > 0)
- System.arraycopy(elementData, index + 1, elementData, index, numMoved);
- // 数组的最后一个位置设置为null 帮助GC
- // 这个操作相当于 elementData[size] = null; size --;
- // 不建议开发中这么写,代码还是要以可读性为准
- elementData[--size] = null;
-
- return oldValue;
- }
-
- private void rangeCheck(int index) {
- // index 合法性校验
- if (index >= size) {
- throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
- }
- }
代码还是比较简单易读的,凡是涉及到 index 的首先都是万年不变的参数合法性检查,然后再获取index对应下标的元素;再对index位置后的元素往前移动,最终返回被删除元素值。
需要关注的是:ArrayList的删除操作,都会伴随着数组元素的移动操作(除非你每次都删除最后一个元素,就当我没说),而这种移动操作是有性能上的消耗的;因此也可以看出 ArrayList 并不是那么适合于删除特别频繁的场景。
public boolean remove(Object o) 方法继承于 AbstractCollection 抽象类,移除首个为 o 的元素,返回是否移除成功。
- /**
- * 移除首个为 o 的元素
- * @param o
- * @return boolean
- */
- public boolean remove(Object o) {
- if (o == null) {
- // 从头遍历elementData数组,找到第一个o元素的index,调用fastRemove方法进行删除
- for (int index = 0; index < size; index++)
- if (elementData[index] == null) {
- // 真实执行删除操作的方法·
- fastRemove(index);
- return true;
- }
- } else {
- for (int index = 0; index < size; index++)
- if (o.equals(elementData[index])) {
- fastRemove(index);
- return true;
- }
- }
- return false;
- }
-
- /**
- * 纯粹的移除元素的操作,不做参数校验(由上层调用者自己做参数合法性检查)
- * 操作次数 + 1
- * index后的元素向前移动
- */
- private void fastRemove(int index) {
- // 数组操作次数 + 1
- modCount++;
- // 需要迁移的元素的数量
- int numMoved = size - index - 1;
- if (numMoved > 0) {
- // 元素前移,填充index位
- System.arraycopy(elementData, index+1, elementData, index, numMoved);
- }
- // 清理最后一个位置,设置为null,方便后续的GC
- // 相当于 elementData[size] = null; size --;
- elementData[--size] = null;
- }
方法不难,看看是怎么实现的:首先是从头遍历elementData数组,找到 第一个 匹配的 o 元素,返回其下标,调用 fastRemove(int index)方法,进行删除操作。
需要注意的是:我们可以看出,ArrayList是支持添加为 null 的元素的。
protected void removeRange(int fromIndex, int toIndex) 继承于 AbstractList抽象类,批量移除 [fromIndex, toIndex)位置的多个元素,注意不包括 toIndex 位置的元素。
- /**
- * 移除 [fromIndex, toIndex) 范围内的元素
- * @param fromIndex 包括
- * @param toIndex 不包括
- */
- protected void removeRange(int fromIndex, int toIndex) {
- // 操作次数 + 1
- modCount++;
-
- // toIndex后面的元素都需要被移动到前面
- int numMoved = size - toIndex;
- // 因此可以看到为啥右边是),因为它并没有 + 1
- System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved);
-
- // 计算新的数组长度
- int newSize = size - (toIndex - fromIndex);
- // 数组往前移动后,后面所有空闲的位置都设为null
- for (int i = newSize; i < size; i++) {
- elementData[i] = null;
- }
- // 更新新的长度
- size = newSize;
- }
我们可以从源码中看出为什么右边 toIndex 并没有被包含,因为 arraycopy 方法的第二个参数是从 toIndex开始,并没有 + 1;后面的代码比较简单可直接看注释。
public boolean removeAll(Collection> c) 方法,继承于AbstractCollection抽象类,批量删除指定的多个元素。
- /**
- * 批量删除指定的多个元素
- * @param c
- * @return boolean
- */
- @Override
- public boolean removeAll(Collection> c) {
- // 集合c判断操作
- Objects.requireNonNull(c);
- // 调用真实的删除方法(该方法与 retainAll 其实共用一个逻辑,关键在于参数2:complement的取值是false还是true)
- return batchRemove(c, false);
- }
-
- /**
- * 真实执行删除的方法
- * 批量移除操作
- * @param complement 补充信息,这个方法在removeAll和retainAll中都被使用到,该字段用以确认是remove还是retain
- * 当complement为false的时候,用以删除 当complement为true的时候,用以保留
- */
- private boolean batchRemove(Collection> c, boolean complement) {
- // 定义一个引用变量指向elementData
- final Object[] elementData = this.elementData;
- // r是简单的遍历参数,w是重新填充数组元素的index参数
- int r = 0, w = 0;
- // 是否被修改
- boolean modified = false;
- try {
- for (; r < size; r++) {
- // removeAll是false,retainAll的是true
- // 假设原为 1 2 3 4 5 6,想删除 1 3 5,那么当c.contains(elementData[r]) == true的时候,我不希望它被赋值到新的数组中的,
- // 所以会设置成false,并且他们的相对位置是不变的
- if (c.contains(elementData[r]) == complement) {
- // 相当于是把新的元素从头开始放置
- // elementData[w] = elementData[r]; w++;
- elementData[w++] = elementData[r];
- }
- }
- } finally {
- // 什么时候会进入到这里呢?大概是try的过程中发生了异常
- if (r != size) {
- System.arraycopy(elementData, r, elementData, w, size - r);
- w += size - r;
- }
- // w与size不相等,说明是removeAll
- if (w != size) {
- // 清空数组后面的无用index的元素,帮助GC
- for (int i = w; i < size; i++) {
- elementData[i] = null;
- }
- modCount += size - w;
- size = w;
- modified = true;
- }
- }
- return modified;
- }
代码看起来可能会有点长,但是逻辑还是比较的清晰的,看看代码注释应该问题不大。
删除操作的源码讲解就到这里,主要的流程大体上是:
1. 找到需要删除的元素所在的下标 index,执行删除操作。
2. 下标 index 后的元素向前移动,并且更新 size 等变量。
public int indexOf(Object o) 方法,查找 首个 为指定元素 o 的位置,源码如下:
- /**
- * 查找首个为指定元素 o 的位置
- * @param o 被查找元素
- * @return int 如果存在则返回对应index 不存在返回-1
- */
- @Override
- public int indexOf(Object o) {
- // 从这里我们其实也可以看出,ArrayList是支持存储null值的
- 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;
- }
- }
- }
- // 不存在则返回 -1
- return -1;
- }
代码就是单纯的遍历数组匹配元素返回下标 index,需要额外注意的是:我们可以看出 ArrayList 是支持存储 null 值的。
可能还有小伙伴会用到 lastIndexOf(Object o) 方法,用以查找最后一个为指定元素的位置;代码其实也很相似,只不过是从数组的后面开始遍历罢了。
- /**
- * 查找最后一个为指定元素的下标位置
- * @param o
- * @return int 下标位置,不存在则返回-1
- */
- @Override
- public int lastIndexOf(Object o) {
- // 最后一次出现元素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;
- }
- }
- }
- // 不存在则返回 -1
- return -1;
- }
转换为数组的方法有2个,分别如下:
public Object[] toArray() 将 ArrayList 转换为 Object[] 数组。
public
- /**
- * 将ArrayList转换成Object类型数组
- * @return Object[]
- */
- @Override
- public Object[] toArray() {
- // 返回的是 Object[] 类型,需要注意:转换成数组就相当于是将 ArrayList 的底层elementData暴露出去
- return Arrays.copyOf(elementData, size);
- }
-
-
- public static
T[] copyOf(T[] original, int newLength) { - return (T[]) copyOf(original, newLength, original.getClass());
- }
我们在调用 toArray() 的时候,可能会遇到异常 java.lang.ClassCastException;这是因为 toArray()方法返回的类型是 Obejct[],如果我们将其转换成其他类型,可能就会抛出异常。 这是因为 Java并不支持向下转型,所以当我们需要将 ArrayList转换成数组并且指定类型的时候,应该使用下面的这个方法。
真实的场景中,我们其实更希望返回指定泛型的数组,所以来瞅瞅 public
- /**
- * 将ArrayList转换成指定泛型的数组
- * @param a
- * @return T[]
- */
- @Override
- @SuppressWarnings("unchecked")
- public
T[] toArray(T[] a) { - // <1>如果传入的数组小于 size 的大小,直接拷贝一个新的数组返回
- if (a.length < size) {
- return (T[]) Arrays.copyOf(elementData, size, a.getClass());
- }
- // <2>否则直接拷贝数组即可
- System.arraycopy(elementData, 0, a, 0, size);
- // <3>如果传入的数组大于 size 的大小,则后面都置为null
- if (a.length > size) {
- a[size] = null;
- }
- // 返回传入的a,但是考虑到<1>的时候会返回新的数组,所以即使<2>返回a数组,最好还是按照 a = list.toArray(a); 来使用
- return a;
- }
我们可以看到,当传入的数组a的长度小于 ArrayList 集合的元素的长度时候,直接拷贝生成一个新的数组返回;否则将 elementData 复制到 a 数组中。
考虑到<1>处有可能会返回一个新的数组,所以即使 <2>返回的就是 a数组;使用的时候最好还是按照 a = list.toArray(a)。
contains(Object o) 方法,判断集合中是否包含某个元素。
get(int index) 方法,获取指定位置的元素。
set(int index, E element) 方法,设置指定位置的元素。
clear() 方法,清空数组。
subList(int fromIndex, int toIndex) 方法,创建子数组。
- /**
- * 判断集合中是否含有元素 o
- * 可以看出,contains方法其实就是依赖的 indexOf方法
- * @param o
- * @return boolean
- */
- @Override
- public boolean contains(Object o) {
- return indexOf(o) >= 0;
- }
-
- /**
- * 获取指定index位的元素
- * @param index
- * @return E
- */
- @Override
- public E get(int index) {
- // 参数检查
- rangeCheck(index);
-
- // 相当于直接返回 elementData[index];
- return elementData(index);
- }
-
- /**
- * 设置指定index位置的元素,并且返回该index位旧元素值
- * 并返回旧元素的值(相当于替换)
- * @param index
- * @param element
- * @return E
- */
- @Override
- public E set(int index, E element) {
- // 参数检查
- rangeCheck(index);
- // 数据的简单操作
- E oldValue = elementData(index);
- elementData[index] = element;
- return oldValue;
- }
-
-
- /**
- * 清空集合
- */
- @Override
- public void clear() {
- // 数组操作次数 + 1
- modCount++;
- // 遍历数组,全部设置为null,同时更新size值
- for (int i = 0; i < size; i++) {
- elementData[i] = null;
- }
-
- size = 0;
- }
-
-
- /**
- * 创建子数组
- * 注意: subList并不是只读数组,而是和父数组共享相同的 elementData 的数组,换句话说,对subList的操作会影响到 父数组
- * 只不过是fromIndex 和 toIndex限制了查看的范围
- * @param fromIndex 开始下标
- * @param toIndex 结束下标
- * 是一个 [fromIndex, toIndex)的效果
- */
- public List
subList(int fromIndex, int toIndex) { - subListRangeCheck(fromIndex, toIndex, size);
- return new SubList(this, 0, fromIndex, toIndex);
- }
通过 Interator 迭代器遍历
- Integer value = null;
- Iterator it = list.iterator();
- while (it.hasNext()) {
- value = (Integer) it.next();
- }
随机访问,通过索引值遍历,由于 ArrayList 实现了 RandomAccess 接口,所以它支持通过索引值去随机访问元素
- Integer value = null;
- int size = list.size();
- for (int i = 0; i < size; i++) {
- value = (Integer) list.get(i);
- }
for循环遍历
- Integer value = null;
- for (Integer i: list) {
- value = i;
- }
测试比较三者的效率,得出在遍历 ArrayLis t的时候,使用随机访问(即通过索引访问)的效率最高,而使用迭代器的效率最低。
ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。