ArrayList 源码分析和自定义ArrayList实现
概述
ArrayList 是基于数组实现的,是一个能自动扩展的动态数组。
ArrayList 是线程不安全的,多线程情况下添加元素会出现数组越界的情况,而且数组赋值操作不是原子操作,会导致多线程情况下数据混乱。
ArrayList 实现了 Serializable 接口,支持序列化;
实现了 Cloneable 接口,支持克隆;
实现了 RandomAccess 接口,支持通过下标进行快速随机访问;
ArrayList 默认大小为 10 ,扩容基数为 50%,而扩容的代价是高昂的,可以通过手动调用 ensureCapacity 方法实现扩容,以减少自动扩容次数。
ArrayList 通过采用 Fail-Fast 快速失败机制,面对并发修改时,迭代器可以通过抛出 ConcurrentModificationException 异常快速失败,而不会在某个未知的时间发生不确定的行为。

类属性

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * 默认初始大小 */ private static final int DEFAULT_CAPACITY = 10; /** * 用于空实例的共享空数组实例。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** *用于默认大小的空实例的共享空数组实例。我们 *将其与空元素数据区分开来,以了解何时充气 *添加第一个元素。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** ArrayList 的元素存储在其中的数组缓冲区。 ArrayList 的容量就是这个数组缓冲区的长度。 添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 ArrayList 都将扩展为 DEFAULT_CAPACITY。 */ transient Object[] elementData; // non-private to simplify nested class access /** * ArrayList的大小(它包含的元素数)。 * * @serial */ private int size; /** *要分配的最大数组大小。 *有些虚拟机在数组中保留一些头字。 *尝试分配较大的阵列可能会导致 *OutOfMemoryError:请求的数组大小超过VM限制 */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** 此列表在结构上被修改的次数。迭代器遍历列表时快速失败的参考字段 */ protected transient int modCount = 0; }
构造函数
无参构造函数
ArrayList 中存储的数据在 elementData 中,使用无参构造函数创建 ArrayList 对象时,elementData 会被初始化为一个空数组,当添加数据时会对 elementData 进行扩容为默认大小为 10 的数组。

/** * 构造一个初始容量为 10 的空列表。 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
自定义容量构造函数
根据指定的容量大小构造一个 ArrayList 对象。

/** * 构造一个具有指定初始容量的空列表。 */ 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); } }
列表转 ArrayList

/** * 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表 */ public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); // Collection 列表转数组 if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; // 如果是 ArrayList 则直接赋值 } else { // 其他类型则转为 Object 数组再赋值 elementData = Arrays.copyOf(a, size, Object[].class); } } else { // Collection 列表为空则直接赋值为空数组 elementData = EMPTY_ELEMENTDATA; } }
核心方法
add 方法
add(E):boolean
最常用的 add 方法,将元素追加到列表末尾。

/** * 将指定的元素附加到此列表的末尾。 */ public boolean add(E e) { // 列表扩容、操作数(modCount)修改 ensureCapacityInternal(size + 1); // Increments modCount!! // 添加元素到指定位置 // 这不是一个原子操作,Size++ 操作会返回自增前的 Size 值 elementData[size++] = e; return true; }
add(int, E):void
在指定位置插入元素(不常用)

/** 在此列表中的指定位置插入指定元素。 将当前在该位置的元素(如果有)和任何后续元素向右移动(向它们的索引添加一个)。 */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
addAll(Collection):boolean
将列表元素添加到 ArrayList 末尾(不常用)

/** 按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾。 如果在操作进行时修改了指定的集合,则此操作的行为未定义。 这意味着如果指定的集合是这个列表,并且这个列表是非空的,那么这个调用的行为是未定义的。) */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
addAll(int index, Collection):boolean
添加列表元素到指定位置(不常用)

/** 从指定位置开始,将指定集合中的所有元素插入此列表。 将当前在该位置的元素(如果有)和任何后续元素向右移动(增加它们的索引)。 新元素将按照指定集合的迭代器返回的顺序出现在列表中。 */ public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount 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; }
add(E) 方法的具体实现
每次添加元素前,都会判断数组是否需要扩容。之后才会执行代码
elementData[size++] = e;
- 数组会被判断为空数组,这时会被扩容为默认大小 10。
- 如果使用了自定义大小的构造函数,那么会判断自定义的容量大小和默认容量大小,如果小于默认容量大小,还是会进行扩容到默认大小。

/** minCapacity 是当前数组实际存储元素数量 +1,也就是执行本次添加元素操作,需要的最小数组容量大小 */ private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } /** 判断是否需要扩容 */ private void ensureExplicitCapacity(int minCapacity) { // 记录操作数 modCount++; // 如果当前需要的最小数组容量大小 > 当前数组容量,那么就需要对数组容量重新调整 // 也就是数组已经添加不了下一个元素了,需要要扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** 返回合适的初始化容量 */ private static int calculateCapacity(Object[] elementData, int minCapacity) { // 判断是否为空数组,如果是空的,说明是首次添加元素,此时数组还没有初始化,没有容量 // 那么就需要判断期望的最小容量(minCapacity)与默认容量 10 的大小,返回最大值 // 由于这个方法被 ensureCapacity(int) 调用了,所以需要判断大小,如果是未被调用公开,就直接返回 10 好了 // ensureCapacity(int) 可以手动调整数组容量,以减少 ArrayList 自动扩容的次数 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } /** ArrayList 扩容的核心方法 */ private void grow(int minCapacity) { // overflow-conscious code // 把当前数组容量赋给临时变量 int oldCapacity = elementData.length; // 新的容量是当前容量的 1.5 倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果新的容量小于最小期望容量,就说明新的容量值是不合理的, // 把最小期望容量作为本次扩容的容量值, if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新的容量值大于数组最大容量值(int 类型最大值 - 8)2^31 - 1 - 8 // 那么需要对新容量设置合理的最大值 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 把旧数组拷贝到新容量的数组,大于原来长度的填充 null elementData = Arrays.copyOf(elementData, newCapacity); } /** 返回数组扩容容量最大值 */ private static int hugeCapacity(int minCapacity) { // 如果最小期望容量为负数,说明最小期望容量已经超过 int 类型最大值(2^31 - 1) // int 类型(范围-2^32 ~ 2^31-1)表示的 2^31 是负数, // 以二进制表示,其实int的数值范围是一个圈,超出最大值变成负数,低于最小值变成正数 // 相关知识需要了解二级制、补码 // 所以这里如果为负数,说明最小期望容量溢出,会抛出异常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // 如果不为负数,那么最小期望容量在数组最大容量(2^31 - 1 - 8)和 int 类型最大值(2^31 - 1)之间 // 选择一个合理的最大值 // MAX_ARRAY_SIZE 为 int 类型最大值 -8 是因为某些 JVM 在数组中保留一些头字, // 超过这个值可能会抛出 OOM return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
remove 方法
ArrayList 的删除方法有以下几个:
remove(int):E
remove(Object):boolean
removeAll(Collection):boolean
removeIf(Predicate):boolean
removeRange(int,int):void
这里只分析常用的几个方法。
remove(int):E
根据索引删除指定元素,利用 System.arraycopy 方法采取复制-覆盖-置空的方式删除元素。其具体原理在源码中有注释分析。
源码分析

/** 移除此列表中指定位置的元素。 将任何后续元素向左移动(从它们的索引中减去一个)。 */ public E remove(int index) { // 检查索引是否越界 rangeCheck(index); // 数组操作次数 +1 modCount++; // 需要删除的元素赋给临时变量 E oldValue = elementData(index); // 需要移动的元素个数 // 举个栗子:数组长度为 10 ,其中有 6 个元素,现在删除第 3 个元素(下标为 2), // 那么 nmMoved = 6 - 2 - 1 = 3 需要移动两个元素 // 如果要删除第 8 个元素(下标为 9),第 8 个元素肯定是不存在的 // 计算 numMoved = 6 - 9 - 1 = -4 移动 -4 个元素?显然不合理 // 所以 numMoved > 0 时才需要移动 // 为 0 或者 < 0 只需要把最后一个元素重置为 null 就可以了 int numMoved = size - index - 1; if (numMoved > 0) // 这里使用 System.arraycopy(Object, int, Object, int, int) 拷贝数组元素到原数组, // 相当于需要移动的元素 覆盖 原来的元素 // System.arraycopy(原数组, 原数组拷贝起始位置,目标数组,目标数组拷贝起始位置,拷贝长度(这个长度包括起始位置)) // 这句拷贝代码就是从原数组 删除索引+1 的位置 拷贝两个元素 到 删除索引 的位置, // 那么数组最后的元素就是无效的了,后面那句代码就是把无效元素重置为 null 了 // 从而达到类似元素移动的效果。 System.arraycopy(elementData, index+1, elementData, index, numMoved); // 把最后的元素重置为 null, // 那么此时这个元素将不再被数组对象引用,下次 GC 时这个对象将可能被当作垃圾清除 elementData[--size] = null; // clear to let GC do its work // 返回需要删除的索引对应的值 return oldValue; } /** 检查给定的索引是否在范围内 */ private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
图解分析

remove(Object):boolean
匹配指定元素并删除,但是匹配的自定义对象 Object 需要实现 equals 方法,具体在代码分析中有体现。
源码分析

/** 从此列表中删除第一次出现的指定元素(如果存在)。 如果列表不包含该元素,则它保持不变。 更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果这样的元素存在)。 如果此列表包含指定的元素(或等效地,如果此列表因调用而更改),则返回true 。 */ public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) // 找到第一个 null 元素 if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) // 匹配第一个指定的元素 // 自定义对象需要重写 equals 方法,否则默认调用超类 Object 的 equals 方法 // Object 的 equals 判断的是对象的内存地址 // 对象 o 和 elementData[index] 的内存地址肯定是不同的 // 所以需要重写 equals if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /** 跳过边界检查并且不返回移除的值的私有移除方法。 这个方法和 remove(int) 的实现是相同的,只不过是私有的且没有返回值 */ 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 }
removeAll(Collection):boolean
批量删除数组中未匹配的元素,参数 Collection 是元素对照列表,在对照列表中存在的元素将被清除。
需要注意的是,如果数组中存在与对照列表类型不一致或其它情况导致匹配出现异常的,匹配将会终止,且未匹配的元素将被保留在原数组。具体在代码分析中体现
源码分析

/** 从此列表中删除包含在指定集合中的所有元素。 */ public boolean removeAll(Collection<?> c) { // 检查对象不为 null Objects.requireNonNull(c); return batchRemove(c, false); } /** 批量删除匹配列表之中或者之外的数组中的元素 complement 是否在列表之中 */ private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; // r 为 数组实际元素大小, w 为包含/不包含的元素个数 boolean modified = false; try { for (; r < size; r++) // 符合 contains == true/false 的元素被移动到临时数组的左端 // 同时更新 w 的值 if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. // 如果上面的 contains 抛出异常,将在这里进行处理 // size - r 是上面循环 contains 抛出异常时尚未匹配的元素个数 // 把这部分尚未匹配的元素追加到 w 后,也就是已经匹配了(包含/不包含在 c 中)的元素 // 然后把 w 加上尚未匹配的元素个数 // 此时 w = 匹配了(包含/不包含在 c 中)的元素个数 + 抛异常尚未匹配的元素个数 if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } // 此时数组中的元素就是 w 的那些元素 + 尚未匹配的元素 + 未被移动的元素(w 中的元素和尚未匹配的元素都是向左移动了的) // 这里清理的就是那些未移动的元素,让 GC 可以回收 // 同时修改数组实际元素个数为 w if (w != size) { // clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
图解分析

retainAll(Collection):boolean
这是 removeAll(Collection) 方法的逆方法,包含在 Collection 对照列表的将被保留,未包含的将被删除,其实现方式与 removeAll(Collection) 方法完全一样。

/** 保留列表中包含的元素 */ public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); }
图解分析

set 方法
用指定元素替换索引位置的元素
源码分析

/** 用指定的元素替换此列表中指定位置的元素。 */ public E set(int index, E element) { // 检查索引越界 rangeCheck(index); // 获取索引位置元素 E oldValue = elementData(index); // 替换索引位置元素 elementData[index] = element; // 返回原来的对象 return oldValue; }
get 方法
获取索引位置元素
源码分析

/** 返回列表中指定位置的元素。 */ public E get(int index) { // 检查索引越界 rangeCheck(index); // 获取索引位置元素 return elementData(index); } // 强制类型转换 @SuppressWarnings("unchecked") E elementData(int index) { return (E) elementData[index]; }
copy 方法
重写超类 Object 的 copy 方法,浅拷贝一个 ArrayList 实例

/** 返回该ArrayList实例的浅副本。(元素本身不会被复制。) 浅拷贝 */ public Object clone() { try { // 调用超类 Object 的 clone() 方法,创建一个 ArrayList 实例 ArrayList<?> v = (ArrayList<?>) super.clone(); // 拷贝当前数组到新实例 ArrayList // 内部使用反射创建了一个长度为 size 的数组对象 // 使用 System.arraycopy 拷贝原数组元素到新数组 // System.arraycopy 是浅拷贝。 v.elementData = Arrays.copyOf(elementData, size); // 重置数组修改次数 v.modCount = 0; // 返回克隆对象 return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
indexOf(Object):int

/** 通过 for 循环全列表扫描,效率及其低下,可以换成 Collections 工具类的 binarySearch 方法 返回此列表中指定元素第一次出现的索引,如果此列表不包含该元素,则返回 -1。 */ 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; }
RandomAccess 接口
RandomAccess 是一个标记接口。Conllections 工具类的 binarySearch 方法,可以根据该标记接口选择遍历方式。具体在代码分析中体现。

/** List实现使用的标记接口来指示它们支持快速(通常是恒定时间)随机访问。 */ public interface RandomAccess { }
binarySearch 源码分析

/** 使用二进制搜索算法搜索指定对象的指定列表。 在进行此调用之前,必须根据其元素的自然顺序(如sort(List)方法)将列表按升序sort(List) 。 如果未排序,则结果未定义。 如果列表包含多个与指定对象相等的元素,则无法保证会找到哪一个。 此方法在 log(n) 时间内运行,用于“随机访问”列表(提供近乎恒定时间的位置访问)。 如果指定的列表未实现RandomAccess接口并且很大,则此方法将执行基于迭代器的二进制搜索,执行 O(n) 链接遍历和 O(log n) 元素比较。 */ public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) { // 列表继承了 RandomAccess 接口或者列表元素小于 5000,使用基于索引的二分法查找 // 否则使用迭代器的二分法查找 // 比如 LinkedList 这种链表式列表,没有继承 RandomAccess 接口,使用迭代器查找效率更高 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) return Collections.indexedBinarySearch(list, key); else return Collections.iteratorBinarySearch(list, key); } /** 基于索引二分法查找 */ private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) { int low = 0; int high = list.size()-1; while (low <= high) { int mid = (low + high) >>> 1; // 根据索引获取元素 Comparable<? super T> midVal = list.get(mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found } /** 基于迭代器的二分法查找 */ private static <T> int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key) { int low = 0; int high = list.size()-1; // 获取列表的迭代器对象 // 这里会返回一个 ListItr 对象,就是一个迭代器 ListIterator<? extends Comparable<? super T>> i = list.listIterator(); while (low <= high) { int mid = (low + high) >>> 1; /** 从迭代器遍历查找对象 */ Comparable<? super T> midVal = get(i, mid); int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found } /** 通过重新定位指定的列表 listIterator 从给定列表中获取第 i 个元素。 */ private static <T> T get(ListIterator<? extends T> i, int index) { T obj = null; // 每次迭代查找都会根据 next 计算 nextIndex 的值 // 这在超大列表查找时,不必每次都从列表开头查找, // 而是根据上次查找的末尾索引计算下次开始查找的开始索引 nextIndex // 所以迭代器查找超大列表或链式列表时效率更高 int pos = i.nextIndex(); if (pos <= index) { do { obj = i.next(); } while (pos++ < index); } else { do { obj = i.previous(); } while (--pos > index); } return obj; }
Fail-Fast 快速失败机制
ArrayList 的私有内部类 ListItr ,是 ArrayList 的迭代器实现类。
线程使用 ArrayList 的迭代器时,都会实例化一个 Itr 的私有内部类,
Itr 实现了 Iterator 接口,ListItr 继承了 Itr 类并实现接口 ListIterator 接口,继承关系如下:

实例化 Itr 类时,会使用当前 ArrayList 的 modCount 属性给 expectedModCount 初始化。
每次使用迭代器的 next、previous、remove、add、set等方法时都会调用 checkForComodification 方法
判断 Arraylist 对象的 modCount 值与 Itr 迭代器对象的 expectedModCount 值比较是否一致,
如果不相等,则为避免迭代产生不正确的结果,会立即抛出 ConcurrentModificationException 异常。
这就是为什么 ArrayList 的所有与列表操作有关的方法都会加上 modCount++ 这句代码,
目的就是一旦检测到列表发生改变就能立即失败,哪怕是在多线程环境下,这个机制依旧是有效的。
这就是 ArrayList 所谓的快速失败机制。
checkForComodification 方法源码

final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
源码分析回顾
ArrayList 是一个基于数组实现的可动态扩展的集合容器。它不是线程安全的。它的基本操作(CRUD)还是比较简单的,对于 ArrayList 的源码分析提炼出以下值得借鉴的点:
1. 它的 add、remove 以及扩容方法核心都是基于数组的复制、覆盖、重置等实现的。比如 fastRemove 方法就是很好的示例
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 }
2. Fail-Fast 快速失败机制,就是基于操作标识字段 modCount 实现的,每个线程通过迭代器遍历 ArrayList 都会实例化一个迭代器实例,所以在多线程情景下,多个迭代遍历不会影响彼此,可一旦 modCount 被某个线程修改,那么所有线程的遍历都会快速失败。
关于快速失败机制,核心就是 modCount 标志字段和工厂模式下的 Itr 迭代器实例。所以可以借鉴在工厂模式创造的对象中加入标识字段,这样就可以通过字段值的变化监控所有的实例。
3. ArrayList 实现的 RandomAccess 接口,提供的快速随机访问的能力,其实质就是使用二分法(binarySearch)取代全列表遍历(indexOf),那么在列表查找方面可以借鉴此实现。
ArrayList 模拟代码(手写代码)

package sx.project.cases; import java.io.Serializable; import java.util.AbstractList; import java.util.RandomAccess; /** <p style="color:rgb(0,255,0);">ArrayList 的自定义实现</p> ArrayList 是一个基于数组的可扩展的动态数组,所以需要实现以下功能<br/> 自动扩展、add、remove、set、get<br/> 继承 AbstractList 是为了实现 List 的装饰模式实现类 实现 RandomAccess 让 MyList 支持快速随机访问的能力 实现 Cloneable 让 MyList 实现拷贝 实现 Serializable 让 MyList 可以序列化和反序列化 **/ public class MyList<T> extends AbstractList<T> implements Serializable, RandomAccess, Cloneable { private static final long serialVersionUID = 169823782352696015L; /** * <p style="color:rgb(127,255,212);">数组默认大小</p> */ private static final int DEFAULT_CAPACITY = 10; /** * <p style="color:rgb(127,255,212);">数组最大容量/p> */ private static final int MAX_CAPACITY = Integer.MAX_VALUE - 8; /** * <p style="color:rgb(127,255,212);">数组</p> */ private T[] array; /** * <p style="color:rgb(127,255,212);">数组元素数量</p> */ private int size; /** * <p style="color:rgb(127,255,212);">数组最小期望容量</p> */ private final int min_capacity = size + 1; /** * <p style="color:rgb(127,255,212);">创建一个指定容量的 MyList</p> */ @SuppressWarnings(value = "unchecked") public MyList (int initialCapacity) { if (initialCapacity < 0) { throw new IllegalArgumentException("Illegal Capacity " + initialCapacity); } else if (initialCapacity == 0) { array = (T[]) new Object[DEFAULT_CAPACITY]; } else { if (initialCapacity < DEFAULT_CAPACITY) { array = (T[]) new Object[DEFAULT_CAPACITY]; } else { array = (T[]) new Object[initialCapacity]; } } } /** * <p style="color:rgb(127,255,212);">创建一个默认容量的 MyList</p> */ public MyList () { this(DEFAULT_CAPACITY); } /** * <p style="color:rgb(255,165,0);">在列表末尾添加元素</p> * @param element 添加的元素 * @return 是否添加成功 */ public boolean add(T element) { add(size, element); return true; } /** * <p style="color:rgb(255,165,0);">在列表指定位置添加元素</p> * @param index 指定位置下标 * @param element 添加的元素 */ public void add(int index, T element) { // 判断是否越界 rangeCheck(index); // 判断是否需要扩容 capacityInternal(); // 拷贝数组 System.arraycopy(array, index, array, index + 1, size - index); // 新元素插入指定位置 this.array[index] = element; // 数组实际容量 +1 size++; } /** * <p style="color:rgb(255,165,0);">删除指定位置的元素</p> * @param index 指定位置的下标 * @return 删除前的元素 */ public T remove(int index) { rangeCheck(index); T oldElement = array[index]; int moveNum = size - index - 1; if (moveNum > 0) { System.arraycopy(array, index + 1, array, index, moveNum); } array[--size] = null; return oldElement; } /** * <p style="color:rgb(255,165,0);">删除第一个匹配的元素</p> * @param element 被删除的元素 * @return 是否删除 */ public boolean remove(Object element) { if (element == null) { for (int index = 0; index < size; index++) { if (array[index] == null) { remove(index); return true; } } } else { for (int index = 0; index < size; index++) { if (array[index].equals(element)) { remove(index); return true; } } } return false; } /** * <p style="color:rgb(255,165,0);">替换指定位置的元素</p> * @param index 指定位置的下标 * @param element 替换的元素 * @return 替换前的元素 */ public T set(int index, T element) { rangeCheck(index); T oldElement = array[index]; array[index] = element; return oldElement; } /** * <p style="color:rgb(255,165,0);">获取指定位置的元素</p> * @param index 指定位置的下标 * @return 获取的元素 */ public T get(int index) { rangeCheck(index); return array[index]; } /** * <p style="color:rgb(255,165,0);">拷贝当前的 MyList</p> * @return 拷贝对象 */ @SuppressWarnings(value = "unchecked") @Override protected Object clone () throws CloneNotSupportedException { try { MyList<T> list = (MyList<T>) super.clone(); T[] array = (T[]) new Object[size]; System.arraycopy(this.array, 0, array, 0, size); list.array = array; return list; } catch (CloneNotSupportedException e) { throw new InternalError(); } } @Override public String toString () { StringBuilder sb = new StringBuilder("MyList["); for (int index = 0; index < size; index++) { sb.append(array[index] == null ? "null" : array[index].toString()); if (index < size - 1) { sb.append(", "); } } sb.append("]"); return sb.toString(); } /** * <p style="color:rgb(127,255,212);">清除所有元素,以便 GC 可以回收</p> */ public void clear() { for (int index = 0; index < size; index++) { array[index] = null; } size = 0; } /** * <p style="color:rgb(127,255,212);">获取当前数组实际大小</p> */ public int size() { return size; } /** * <p style="color:rgb(127,255,212);">判断 MyList 是否为空</p> */ public boolean isEmpty() { return this.size == 0; } /** * <p style="color:rgb(255,165,0);">判断是否需要扩容</p> */ private void capacityInternal(){ if (size + 1 > array.length) { resize(); } } /** * <p style="color:rgb(255,165,0);">扩容核心</p> */ @SuppressWarnings(value = "unchecked") private void resize() { int oldCapacity = array.length; // 新容量为原始容量的 1.5 倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量小于最小期望容量,则重新赋值 // 大于最大容量,则赋值最大容量 // 新容量小于 0,此时将抛出内存溢出的错误 if (newCapacity < 0) { throw new OutOfMemoryError(); } if (newCapacity < min_capacity) { newCapacity = min_capacity; } else if (newCapacity > MAX_CAPACITY) { newCapacity = MAX_CAPACITY; } // 创建新容量数组 T[] newArray = (T[]) new Object[newCapacity]; // 数组元素全量拷贝 System.arraycopy(array, 0, newArray, 0, size); // 指向新数组 array = newArray; } /** * <p style="color:rgb(127,255,212);">检查索引是否越界</p> */ private void rangeCheck (int index) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException(outOfBoundMsg(index)); } } private String outOfBoundMsg(int index){ return "Index: "+index+", Size: "+size; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)