ArrayList继承与实现关系
先看看这个类的继承与实现:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
AbstractList
继承了一个AbstractList类,是一个List接口的抽象实现类。AbstractList做了什么事?
- 实现了
List
接口:AbstractList
实现了List
接口,因此它具备了List
接口的所有方法,包括添加、删除、获取元素等操作。这使得具体的List
实现类可以直接继承AbstractList
,而无需从零开始实现List
接口的方法。 - 提供了基本的抽象方法:
AbstractList
提供了一些基用本的抽象方法,例如get(int index)
和set(int index, E element)
等。这些方法的具体实现由子类来完成,以满足不同List
实现类的需求。 - 支持修改操作:
AbstractList
还提供了一些辅助方法,用于支持修改操作。例如,add(int index, E element)
和remove(int index)
方法可以在指定位置添加和删除元素。这些方法的具体实现会利用其他方法来实现。
List
实现List接口,为什么已经继承了实现List接口的AbstractList抽象类,为什么还要实现List接口?
ArrayList
类再次实现List
接口是为了提供get(int index)
和set(int index, E element)等
这些具体的抽象方法的实现,确保ArrayList
类满足List
接口的所有合约要求。通过实现List
接口,ArrayList
类可以保证它所提供的方法与List
接口的要求一致,并且与其他实现了List
接口的集合类可以无缝替换和使用。
RandomAccess
RandomAccess
是一个标记接口,它没有任何方法定义。在Java集合框架中,实现了RandomAccess
接口的类表示它们支持快速、随机访问元素的能力。
ArrayList
,使用索引访问元素的效率要比使用迭代器进行遍历的效率高。这是因为RandomAccess
接口的存在可以让算法在处理集合时,根据其是否实现了RandomAccess
接口来选择不同的遍历方式。具体来说,当一个集合实现了RandomAccess
接口时,算法会优先选择通过索引来访问元素,因为这种方式可以在常数时间内进行,即O(1)的时间复杂度。而如果一个集合没有实现RandomAccess
接口,算法会选择使用迭代器进行遍历,因为使用索引来访问元素可能需要较高的时间复杂度,例如链表结构- 需要注意的是,并非所有实现了
List
接口的集合类都实现了RandomAccess
接口。仅当集合具备快速随机访问元素的能力时,才应该实现RandomAccess
接口。
Cloneable
Cloneable
是一个标记接口,在Java中被用来指示一个类可以被克隆。通过实现Cloneable
接口,类可以表明它支持克隆(复制)自身的能力。
-
ArrayList
实现了Cloneable
接口,这意味着可以通过clone()
方法来创建一个ArrayList
的副本。这个副本将包含与原始ArrayList
相同的元素,但是是完全独立的对象。需要注意的是这种复制方式是浅拷贝的方式,这意味着两个ArrayList
将共享相同的元素,如果修改其中一个ArrayList
的元素,另一个ArrayList
也会受到影响。
Serializable
Serializable是一个标记接口,在Java中被用来指示一个类可以被序列化操作的。通过实现Cloneable
接口,类可以表明它支持将自身进行序列化操作的能力。
- 在Java中如果要实现对象的IO读写操作,都必须实现Serializable接口。
ArrayList成员变量
/** * 序列号ID,用于序列化的版本控制 */ private static final long serialVersionUID = 8683452581122892189L; /** * 这是一个常量,表示ArrayList的默认容量大小。当我们创建一个新的ArrayList时,如果没有指定* 初始容量,它的初始容量将被设置为这个值。 */ private static final int DEFAULT_CAPACITY = 10; /** * 空对象数组,EMPTY_ELEMENTDATA是为了优化创建ArrayList空实例时产生不必要的空数组,使得 * 所有ArrayList空实例都指向同一个空数组。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 空对象数组,DEFAULTCAPACITY_EMPTY_ELEMENTDATA是为了确保无参构成函数创建的实例在添 * 加第一个元素时,最小的容量是默认大小10。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 这是一个对象数组,用于存储ArrayList中的元素。当我们向ArrayList中添加元素时,它们会被放* 入这个数组中。这个数组的大小会根据需要进行动态调整,以适应ArrayList中的元素数量。 * transient修饰表示elementData序列化的时候会被忽略;并使用ArrayList提供了两个用于序列化* 和反序列化的方法readObject和writeObject把保存的数据写入流或从流读出。 */ transient Object[] elementData; /** * 这是一个整数变量,用于记录ArrayList中实际包含的元素数量。当我们添加、删除元素时,size的* 值会相应地增加或减少。 */ private int size; /** * 最大容量 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
ArrayList构造函数
无参构造函数
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//对象数组赋值成空对象数组 }
有参构造函数
- 通过传入int类型的长度创建ArrayList
//传入初始化容量initialCapacity 【new ArrayList(容量)】 public ArrayList(int initialCapacity) { //判断传入的容量大小 if (initialCapacity > 0) { //创建一个容量为initialCapacity的对象数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //将elementData 指向空对象数组EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { //非法传参,容量不可以为负数 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
2.通过传入Collection子类集合创建ArrayList
public ArrayList(Collection<? extends E> c) { //将传入的集合转成对象数组并赋值给elementData elementData = c.toArray(); //判断elementData的长度是否为0并把长度赋值给size if ((size = elementData.length) != 0) { // 判断elementData 的类型,由于泛型中类型擦除的原因,他有可能返回的不是一个 // Object[] if (elementData.getClass() != Object[].class) /**如果类型不是对象数组,则通过Arrays.copyOf()将elementData复制成一个新的类型为Object[]的数组*/ elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 如果传入的集合长度为0,直接将elementData 指向EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } }
ArrayList部分成员方法
对ArrayList本身进行操作
-
void trimToSize(),用于减小ArrayList的容量,将其容量调整为当前元素数量所需的最小容量。
public void trimToSize() { // 记录ArrayList的结构性修改次数,以便在迭代器中检测到这个修改。 modCount++; /** 判断当前size(ArrayList中的元素数量)是否小于elementData数组的长度(这里会有扩容多出来的长度),如果小于,说明当前元素数量小于数组长度,可以将数组的长度调整为当前元素数量,以节省内存空间。*/ if (size < elementData.length) { /** 三目表达式得出的结构会赋值给elementData, 使用Arrays.copyOf()方法来创建一个新的、具有指定长度的数组。如果当前元素数量为0(即ArrayList为空),则新数组将使用EMPTY_ELEMENTDATA(一个空数组)来初始化;如果当前元素数量不为0,则新数组将是在elementData数组的基础上复制出指定长度的新数组。*/ elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
-
int size(),用于获取当前ArrayList中的元素个数。(并非容量)
public int size() { return size; }
-
boolean isEmpty(),用于判断当前ArrayList是否为空(没有元素)。
public boolean isEmpty() { return size == 0; }
-
void clear(),用于清空ArrayList
public void clear() { modCount++; /** 通过一个循环遍历集合中的每个元素,将其设置为null。这是为了释放对每个元素的引用,让垃圾回收器可以回收这些对象占用的内存空间。JVM使用可达性分析算法判断一个内存中哪些对象是垃圾,通过设置对元素的引用设置为null,断开原本栈中指向堆内存的引用,使得堆中存储对象的内存块不可达。*/ for (int i = 0; i < size; i++) elementData[i] = null; //将size设置为0,表示集合中不再包含任何元素 size = 0; }
对ArrayList数据进行操作
-
int indexOf(Object o),用于获取o在ArrayList中第一次出现的下标。
public int indexOf(Object o) { /**这里要区分一下,如果o为空就不能调用equals()方法进行比较,所有判断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; }
-
int lastIndexOf(Object o),用于获取o在ArrayList中最后一次出现的下标。
/**与indexOf(Object o)区别是lastIndexOf(Object o)是倒叙遍历的手法从ArrayList后往前寻找匹配元素,实现获取o在ArrayList中最后一次出现的下标*/ 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; }
-
boolean contains(Object o),用于判断数组是否包含某个元素
public boolean contains(Object o) { /**直接调用indexOf(o),如果匹配到了元素就会返回元素的下标,反之返回-1,通过判断大于或小于0实现判断是否包含某个元素的功能。*/ return indexOf(o) >= 0; }
-
E get(int index),用于传入下标,获取ArrayList中的元素
public E get(int index) { /**这里调用rangeCheck()方法传入下标,作用是检查传入的下标是否在ArrayList的下标范围中,如果不在会抛出异常IndexOutOfBoundsException。*/ rangeCheck(index); //返回elementData中保存的元素 return elementData(index); }
-
E set(int index, E element),用于传入下标和值,修改某个下标中存储的元素值
public E set(int index, E element) { /**这里调用rangeCheck()方法传入下标,作用是检查传入的下标是否在ArrayList的下标范围中,如果不在会抛出异常IndexOutOfBoundsException。*/ rangeCheck(index); /**获取当前下标存储的旧值*/ E oldValue = elementData(index); /**赋新值*/ elementData[index] = element; return oldValue; }
-
boolean add(E e)和void add(int index, E element),用于向ArrayList中添加元素,后者可以自定义插入的位置;扩容机制就发生在这里
public boolean add(E e) { /**调用ensureCapacityInternal,作用是让调用了默认构造函数创建的ArrayList能够在第一次add元素的时候,扩容默认的容量10。*/ ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } public void add(int index, E element) { rangeCheckForAdd(index); // ensureCapacityInternal() 里调用了方法进行扩容 ensureCapacityInternal(size + 1); /**System.arraycopy(源数组,源下标,目标数组,目标下标,复制源数组多少个值),其作用是将插入的指定下标的值以及后面的值进行向后移动,目的是腾出index这个下标下的元素 例如 有一个elementData= [1,2,3,null,null],容量是5,size=3 在index=1的下标位置插入元素element=4,调用 System.arraycopy()后,就会将2,3向后移动,变成[1,2,2,3,null],在elementData[index] = element,最终[1,4,2,3,null] */ System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
-
void ensureCapacityInternal(int minCapacity),确保默认构造函数创建的空对象数组,在第一次扩容时是从默认容量开始扩容的
private void ensureCapacityInternal(int minCapacity) { //判断elementData 是否是默认构造函数创建的空对象数组 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果是就直接将默认的容量DEFAULT_CAPACITY = 10赋值给minCapacity(元素个数+1的结果) minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //在这个ensureExplicitCapacity判断是否需要扩容 ensureExplicitCapacity(minCapacity); }
-
void ensureExplicitCapacity(int minCapacity),判断是否需要对elementData 扩容
private void ensureExplicitCapacity(int minCapacity) { modCount++; /**判断minCapacity(元素个数+1的结果),意思是添加了元素之后元素的总和,容量最小应该满足的值。minCapacity - elementData.length > 0 如果大于表示超出了当前容量,反之*/ if (minCapacity - elementData.length > 0) // 真正做扩容的方法 grow(minCapacity); }
-
void grow(int minCapacity),对elementData扩容
private void grow(int minCapacity) { //先保存当前elementData中的容量 int oldCapacity = elementData.length; /**扩容的倍数,这里用到了位运算 其实就是除以2 例如 10 >> 1 = 5 ,这个newCapacity是扩容之后的容量,旧值+旧值的1/2,得出newCapacity是oldCapacity 的1.5倍*/ int newCapacity = oldCapacity + (oldCapacity >> 1); // 判断新值是否满足了minCapacity(添加元素后应该有的总容量) if (newCapacity - minCapacity < 0) //如果新值还是不满足,就直接扩容到刚好满足容量的minCapacity,这个值 newCapacity = minCapacity; /**这里判断是否超出了最大扩容量(最大扩容量是Integer.MAX -8,因为有些虚拟机超出这个数会OOM,也就是内存溢出)*/ if (newCapacity - MAX_ARRAY_SIZE > 0) /**这里是,如果真的超过了Integer.MAX_VALUE-8 此时还可以调用hugeCapacity()将容量扩大到Integer.MAX_VALUE*/ newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
结尾
以上就是我对ArrayList底层实现的拙见。