以下是一个自定义的 MyArrayList
类实现,模拟了 Java 中 ArrayList
的核心功能,包括动态扩容、元素增删改查等操作,代码附带详细注释:
import java.util.Arrays;
/**
* 自定义ArrayList实现,模拟动态数组功能
* 支持泛型,实现基本的增删改查及扩容机制
*/
public class MyArrayList<E> {
// 底层存储元素的数组(泛型擦除后为Object[])
private Object[] elementData;
// 当前元素数量(实际存储的元素个数)
private int size;
// 默认初始容量(无参构造器首次扩容时使用)
private static final int DEFAULT_CAPACITY = 10;
// 空数组(用于初始化为空的情况)
private static final Object[] EMPTY_ARRAY = {
};
/**
* 无参构造器:初始化为空数组,首次添加元素时扩容至默认容量10
*/
public MyArrayList() {
this.elementData = EMPTY_ARRAY;
}
/**
* 有参构造器:指定初始容量
* @param initialCapacity 初始容量(必须≥0)
* @throws IllegalArgumentException 若初始容量为负数则抛出异常
*/
public MyArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ARRAY;
} else {
throw new IllegalArgumentException("初始容量不能为负数: " + initialCapacity);
}
}
/**
* 获取当前元素数量
* @return 元素个数
*/
public int size() {
return size;
}
/**
* 判断集合是否为空
* @return 空返回true,否则返回false
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 向集合末尾添加元素
* @param e 要添加的元素
* @return 始终返回true(模拟ArrayList的add方法)
*/
public boolean add(E e) {
// 确保容量足够(不够则扩容)
ensureCapacity(size + 1);
// 存储元素并更新size
elementData[size++] = e;
return true;
}
/**
* 在指定索引位置插入元素
* @param index 插入位置(必须在0~size之间)
* @param element 要插入的元素
* @throws IndexOutOfBoundsException 若索引越界则抛出异常
*/
public void add(int index, E element) {
// 检查索引合法性(插入时索引可等于size,即末尾追加)
checkIndexForAdd(index);
// 确保容量足够
ensureCapacity(size + 1);
// 将index及之后的元素向后移动一位(复制数组)
System.arraycopy(elementData, index, elementData, index + 1, size - index);
// 插入新元素
elementData[index] = element;
size++;
}
/**
* 获取指定索引的元素
* @param index 索引位置
* @return 该位置的元素
* @throws IndexOutOfBoundsException 若索引越界则抛出异常
*/
@SuppressWarnings("unchecked")
public E get(int index) {
checkIndex(index);
return (E) elementData[index]; // 泛型强转(编译期unchecked警告)
}
/**
* 修改指定索引的元素
* @param index 索引位置
* @param element 新元素
* @return 被替换的旧元素
* @throws IndexOutOfBoundsException 若索引越界则抛出异常
*/
public E set(int index, E element) {
checkIndex(index);
E oldElement = get(index);
elementData[index] = element;
return oldElement;
}
/**
* 删除指定索引的元素
* @param index 索引位置
* @return 被删除的元素
* @throws IndexOutOfBoundsException 若索引越界则抛出异常
*/
public E remove(int index) {
checkIndex(index);
E oldElement = get(index);
// 计算需要移动的元素数量(index之后的元素个数)
int moveCount = size - index - 1;
if (moveCount > 0) {
// 将index+1及之后的元素向前移动一位
System.arraycopy(elementData, index + 1, elementData, index, moveCount);
}
// 释放最后一个元素的引用(帮助GC)
elementData[--size] = null;
return oldElement;
}
/**
* 删除指定元素(仅删除第一个匹配项)
* @param o 要删除的元素(可以为null)
* @return 存在并删除返回true,否则返回false
*/
public boolean remove(Object o) {
if (o == null) {
// 处理null元素(null只能用==判断)
for (int i = 0; i < size; i++) {
if (elementData[i] == null) {
fastRemove(i); // 内部快速删除(不重复检查索引)
return true;
}
}
} else {
// 处理非null元素(用equals判断)
for (int i = 0; i < size; i++) {
if (o.equals(elementData[i])) {
fastRemove(i);
return true;
}
}
}
return false;
}
/**
* 清空集合(释放所有元素引用)
*/
public void clear() {
// 遍历并置空所有元素(帮助GC回收)
for (int i = 0; i < size; i++) {
elementData[i] = null;
}
size = 0; // 重置元素数量
}
/**
* 确保内部容量足够(核心扩容逻辑)
* @param minCapacity 所需的最小容量
*/
private void ensureCapacity(int minCapacity) {
// 若当前数组为空(初始状态),则最小容量取默认容量和所需容量的最大值
if (elementData == EMPTY_ARRAY) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 若所需容量超过当前数组长度,则触发扩容
if (minCapacity > elementData.length) {
grow(minCapacity);
}
}
/**
* 扩容核心方法:创建新数组并复制元素
* @param minCapacity 所需的最小容量
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 新容量 = 旧容量 + 旧容量/2(即1.5倍扩容,位运算效率更高)
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 若1.5倍扩容后仍不足,则直接使用所需最小容量
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
// 复制原数组元素到新数组(新数组长度为newCapacity)
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* 检查索引是否越界(用于get/set/remove等操作)
* @param index 要检查的索引
* @throws IndexOutOfBoundsException 若索引<0或≥size则抛出异常
*/
private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException("索引越界:index=" + index + ", size=" + size);
}
}
/**
* 检查添加操作的索引是否越界(插入时索引可等于size)
* @param index 要检查的索引
* @throws IndexOutOfBoundsException 若索引<0或>size则抛出异常
*/
private void checkIndexForAdd(int index) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("索引越界:index=" + index + ", size=" + size);
}
}
/**
* 快速删除(内部使用,跳过索引检查以提高效率)
* @param index 要删除的索引
*/
private void fastRemove(int index) {
int moveCount = size - index - 1;
if (moveCount > 0) {
System.arraycopy(elementData, index + 1, elementData, index, moveCount);
}
elementData[--size] = null;
}
/**
* 重写toString,方便打印集合内容
* @return 集合的字符串表示(格式:[e1, e2, ..., en])
*/
@Override
public String toString() {
if (size == 0) {
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0; i < size; i++) {
sb.append(elementData[i]);
if (i != size - 1) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
}
核心功能说明
底层结构:使用
Object[]
数组存储元素,通过泛型支持任意数据类型(利用类型擦除实现,获取元素时需强转)。容量管理:
- 无参构造器初始化为空数组,首次添加元素时扩容至默认容量
10
。 - 有参构造器可指定初始容量,避免频繁扩容。
- 扩容规则:当元素数量超过当前容量时,新容量为旧容量的
1.5
倍(通过oldCapacity + (oldCapacity >> 1)
计算);若 1.5 倍仍不足,则直接使用所需最小容量。
- 无参构造器初始化为空数组,首次添加元素时扩容至默认容量
核心方法:
- 新增:
add(E e)
(尾插)、add(int index, E element)
(指定位置插入,需移动后续元素)。 - 查询:
get(int index)
(获取指定位置元素)。 - 修改:
set(int index, E element)
(替换指定位置元素)。 - 删除:
remove(int index)
(按索引删除)、remove(Object o)
(按元素删除,支持 null)。 - 工具:
size()
(元素数量)、isEmpty()
(是否为空)、clear()
(清空集合)。
- 新增:
异常处理:对越界索引抛出
IndexOutOfBoundsException
,对负数初始容量抛出IllegalArgumentException
。内存优化:删除元素后主动将末尾元素置为
null
,帮助垃圾回收(GC)释放内存。
使用示例
public class TestMyArrayList {
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
// 添加元素
list.add("Java");
list.add("Python");
list.add(1, "C++"); // 在索引1插入元素
System.out.println(list); // 输出: [Java, C++, Python]
// 修改元素
list.set(2, "JavaScript");
System.out.println(list.get(2)); // 输出: JavaScript
// 删除元素
list.remove(0);
System.out.println(list); // 输出: [C++, JavaScript]
list.remove("JavaScript");
System.out.println(list.size()); // 输出: 1
// 清空集合
list.clear();
System.out.println(list.isEmpty()); // 输出: true
}
}
该实现模拟了 JDK ArrayList
的核心逻辑,适合理解动态数组的底层原理。实际开发中建议使用 JDK 自带的 ArrayList
(经过高度优化,支持更多功能如迭代器、序列化等)。