面试题:不使用集合类如何在Java中实现动态数组?
嘿,我完全懂你当时的困惑!一开始听到“固定大小的数组怎么实现动态”确实会懵,但其实核心思路就是手动模拟集合类的扩容逻辑——当原数组存满时,创建一个更大的新数组,把原数组的元素复制过去,再用新数组替代旧的。加上泛型就能让这个动态数组支持任意类型,和ArrayList的底层原理本质上是一样的!
手动实现泛型动态数组的核心思路
- 底层依赖固定大小的数组,但额外维护一个
size变量记录实际存储的元素个数 - 当元素数量达到当前数组容量时,触发扩容(通常扩容为原容量的2倍,保证操作效率)
- 用泛型
封装,让数组可以存储任意引用类型,同时保证类型安全
完整实现代码
public class GenericDynamicArray<T> { // 底层存储元素的核心数组 private T[] elements; // 当前实际存储的元素个数 private int size; // 默认初始容量 private static final int DEFAULT_CAPACITY = 10; // 默认构造:初始化默认容量的数组 @SuppressWarnings("unchecked") public GenericDynamicArray() { // Java不允许直接创建泛型数组,这里用Object数组强转(泛型擦除特性导致的折中方案) elements = (T[]) new Object[DEFAULT_CAPACITY]; size = 0; } // 自定义初始容量的构造方法 @SuppressWarnings("unchecked") public GenericDynamicArray(int initialCapacity) { if (initialCapacity <= 0) { throw new IllegalArgumentException("初始容量不能小于等于0"); } elements = (T[]) new Object[initialCapacity]; size = 0; } // 添加元素到数组末尾 public void add(T element) { // 先检查容量,不够就扩容 ensureCapacity(); elements[size++] = element; } // 获取指定索引的元素 public T get(int index) { checkIndexValidity(index); return elements[index]; } // 修改指定索引的元素,并返回旧元素 public T set(int index, T newElement) { checkIndexValidity(index); T oldElement = elements[index]; elements[index] = newElement; return oldElement; } // 删除指定索引的元素,并返回被删除的元素 public T remove(int index) { checkIndexValidity(index); T removedElement = elements[index]; // 将索引后的元素向前移动一位,覆盖被删除的位置 System.arraycopy(elements, index + 1, elements, index, size - index - 1); // 清空最后一个位置的引用,帮助GC回收 elements[--size] = null; return removedElement; } // 获取当前实际元素个数 public int size() { return size; } // 获取当前数组的总容量 public int capacity() { return elements.length; } // 检查索引是否越界 private void checkIndexValidity(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("索引越界:" + index + ",当前元素总数:" + size); } } // 确保数组有足够容量存储新元素 private void ensureCapacity() { // 当元素个数等于数组容量时,触发扩容 if (size == elements.length) { int newCapacity = elements.length * 2; // 扩容为原容量的2倍 @SuppressWarnings("unchecked") T[] newElements = (T[]) new Object[newCapacity]; // 高效复制原数组元素到新数组 System.arraycopy(elements, 0, newElements, 0, size); elements = newElements; } } // 测试示例 public static void main(String[] args) { GenericDynamicArray<String> strArray = new GenericDynamicArray<>(); strArray.add("Java"); strArray.add("泛型"); strArray.add("动态数组"); System.out.println("当前元素个数:" + strArray.size()); System.out.println("当前数组容量:" + strArray.capacity()); System.out.println("索引1的元素:" + strArray.get(1)); strArray.set(1, "Generic"); System.out.println("修改后索引1的元素:" + strArray.get(1)); strArray.remove(0); System.out.println("删除后元素个数:" + strArray.size()); System.out.println("删除后索引0的元素:" + strArray.get(0)); // 测试自动扩容 for (int i = 0; i < 15; i++) { strArray.add("测试元素" + i); } System.out.println("扩容后的数组容量:" + strArray.capacity()); } }
关键细节说明
- 泛型的使用:通过
GenericDynamicArray<T>定义泛型类,T可以替换为任意引用类型(比如String、Integer),避免了使用Object数组时的强制类型转换,同时保证类型安全。 - 数组扩容逻辑:
ensureCapacity()是核心,当元素填满当前数组时,创建一个2倍容量的新数组,用System.arraycopy高效复制元素,这也是ArrayList等集合类的底层实现方式。 - 索引合法性检查:所有涉及索引的操作都会先检查索引是否在有效范围内,避免数组越界异常。
其实面试官要的就是这个思路——底层还是用固定数组,但通过手动扩容和泛型封装,对外提供动态数组的功能。当时没想到很正常,毕竟平时都是直接用现成的集合,很少去抠底层实现~
内容的提问来源于stack exchange,提问作者Mehak Batra
相关产品推荐
相关产品推荐

