arraylist底层用object[]存储元素,泛型在编译期被擦除,运行时无类型参数;通过编译器插入强制转换保障类型安全,避免泛型数组创建限制,扩容与序列化均与泛型无关。

ArrayList底层不存泛型,只用Object[]承载元素
Java泛型在编译期被擦除,运行时不存在类型参数。所以ArrayList源码中没有E[]这样的泛型数组,而是声明为transient Object[] elementData。这个数组是实际存储所有元素的容器,无论你写的是ArrayList
这种设计带来两个关键事实:
- elementData.length 是数组容量,size 是当前有效元素个数,二者常不相等
- 添加元素时不检查泛型约束——擦除后只剩Object,类型安全靠编译器在add、get等方法入口处保障
泛型如何“映射”到elementData:靠编译器插入强制转换
当你调用list.get(0)时,源码里返回的是(E) elementData[index]——这里有个显式强转。由于E在运行时是Object,这个转换永远成功,不会抛ClassCastException;真正起作用的是编译器在调用方插入的隐式转换。
例如:
- String s = list.get(0); → 编译器自动补成String s = (String) list.get(0);
- 若list实际存了Integer,这段代码在编译期就报错,根本到不了运行时
为什么不用E[]?避免泛型数组的类型擦除陷阱
如果ArrayList尝试声明E[] elementData,编译会失败或触发unchecked警告。因为Java不允许创建泛型数组(如new E[10]非法),这是类型擦除与数组协变性冲突导致的限制。
ArrayList选择绕过该限制:
- 构造时用new Object[capacity]分配空间
- 用@SuppressWarnings("unchecked")压制elementData = (E[]) new Object[capacity]这类转型警告
- 把类型安全责任交给泛型边界和编译检查,而非运行时数组类型
泛型与扩容逻辑完全解耦
ensureCapacityInternal和grow方法只处理int minCapacity和Object[]复制,全程不涉及E或任何类型信息。扩容就是纯粹的内存操作:计算新长度→新建Object数组→System.arraycopy复制旧内容→替换引用。
这意味着:
- 扩容性能与泛型类型无关,无论存String还是自定义User对象,开销一致
- 序列化时跳过null占位的elementData位置(靠transient+writeObject定制),也不依赖泛型
- 克隆和拷贝都是浅拷贝,复制的是引用,不是类型结构










