Java List 完全指南:从接口特性到四大实现类深度解析
一、介绍
List 是 Java 集合框架(java.util)中有序、可重复的集合接口,继承自Collection接口,是日常开发中最常用的集合类型之一。其核心特征是:元素按插入顺序排列、允许重复元素、支持通过索引访问元素。
特性:
- 有序性:元素的存储顺序与插入顺序一致,可通过索引(0 开始)精准访问 / 修改元素。
- 可重复性:允许添加多个相同的元素(
equals()返回 true 的元素)。 - 支持 null:允许存储 null 元素(不同实现类对 null 的支持略有差异,如
CopyOnWriteArrayList也支持,但Vector同样支持)。 - 索引操作:提供大量基于索引的方法(如
get(int index)、set(int index, E element)),这是与Set最核心的区别。
二、List 接口核心方法
List 继承了Collection的所有方法,并扩展了索引相关的核心方法:
| 方法 | 作用 |
|---|---|
E get(int index) | 获取指定索引的元素 |
E set(int index, E element) | 替换指定索引的元素,返回原元素 |
void add(int index, E element) | 在指定索引插入元素,后续元素后移 |
E remove(int index) | 删除指定索引的元素,返回被删除元素 |
int indexOf(Object o) | 返回元素首次出现的索引,不存在则返回 -1 |
int lastIndexOf(Object o) | 返回元素最后出现的索引,不存在则返回 -1 |
List<E> subList(int fromIndex, int toIndex) | 截取子列表(左闭右开),注意:子列表是原列表的视图,修改会同步 |
ListIterator<E> listIterator() | 返回支持双向遍历的迭代器(可向前 / 向后遍历,还能添加 / 修改元素) |
三、List 主要实现类
Java 提供了多个 List 实现类,适配不同的业务场景,核心有以下 4 种:
1. ArrayList(最常用)
底层实现:基于动态扩容的数组(初始容量 10,扩容时默认扩容 1.5 倍)。
补充:
ArrayList 的本质是可变长度的 Object 数组(JDK 8 及以上),所有元素都存储在这个数组中,数组的「固定长度」特性通过「扩容机制」被封装,对外表现为 “动态列表”。
// ArrayList 核心底层数组(JDK 8 源码核心片段)publicclassArrayList<E>extendsAbstractList<E>implementsList<E>{// 存储元素的底层数组(transient 表示不参与默认序列化)transientObject[]elementData;// 集合中实际元素的数量(≠ elementData.length)privateintsize;// 空数组常量(初始无参构造时使用)privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};}属性 作用 elementData核心存储数组,类型为 Object [],存放所有 ArrayList 元素;初始无参构造时,该数组为「空数组」(DEFAULTCAPACITY_EMPTY_ELEMENTDATA),首次添加元素时才初始化容量。 size记录集合中实际元素个数(而非数组长度),例如数组长度为 10 但只存了 3 个元素,size=3。 DEFAULT_CAPACITY默认初始容量(值为 10),首次添加元素时数组会扩容至 10。 MAX_ARRAY_SIZE数组最大容量(Integer.MAX_VALUE - 8),避免数组占用过多内存导致 OOM。 构造方法:
1.无参构造:
List<String>list=newArrayList<>();2.指定初始容量构造
List<String>list=newArrayList<>(20);3.基于集合构造
List<String>list=newArrayList<>(Arrays.asList("a","b","c"));扩容:
调用
add()/addAll()时,若size + 新增元素数 > elementData.length,触发扩容。// 1. 计算新容量:默认扩容1.5倍intnewCapacity=oldCapacity+(oldCapacity>>1);// 2. 若新容量不足(如新增大量元素),直接扩容至「所需最小容量」if(newCapacity-minCapacity<0)newCapacity=minCapacity;// 3. 若新容量超过MAX_ARRAY_SIZE,修正为Integer.MAX_VALUE(避免溢出)if(newCapacity-MAX_ARRAY_SIZE>0)newCapacity=hugeCapacity(minCapacity);// 4. 拷贝原数组元素到新数组(核心:Arrays.copyOf)elementData=Arrays.copyOf(elementData,newCapacity);/* 扩容倍数:默认 1.5 倍(oldCapacity >> 1 等价于 oldCapacity/2); 扩容开销:扩容需要拷贝数组元素(System.arraycopy),属于 O (n) 操作,因此提前指定初始容量能减少扩容次数,提升性能; 空列表首次扩容:无参构造的 ArrayList 首次 add 时,直接将数组扩容至 10(而非 1.5 倍,因为初始数组长度为 0)。 */核心特点:
- 随机访问快(数组索引直接访问,时间复杂度 O (1));
- 插入 / 删除慢(需移动元素,时间复杂度 O (n));
- 非线程安全(多线程操作会抛出
ConcurrentModificationException); - 支持快速随机遍历,适合读多写少的场景。
使用示例:
List<String>arrayList=newArrayList<>();arrayList.add("Java");arrayList.add("Python");Stringfirst=arrayList.get(0);// 获取第一个元素arrayList.set(1,"C++");// 替换第二个元素2. LinkedList
底层实现:基于双向链表(每个节点存储前驱、后继和元素)。
补充:
LinkedList 的本质是双向循环链表(逻辑上)/ 双向非循环链表(物理实现),每个元素封装为「节点(Node)」,节点间通过前驱 / 后继指针相连,整体无固定长度的数组,而是通过指针动态拼接。
// 私有静态内部类:链表的最小单元privatestaticclassNode<E>{Eitem;// 节点存储的元素Node<E>next;// 指向后继节点的指针(下一个节点)Node<E>prev;// 指向前驱节点的指针(上一个节点)// 节点构造器:初始化前驱、元素、后继Node(Node<E>prev,Eelement,Node<E>next){this.item=element;this.next=next;this.prev=prev;}}属性:
publicclassLinkedList<E>extendsAbstractSequentialList<E>implementsList<E>,Deque<E>{transientintsize=0;// 链表中实际元素个数transientNode<E>first;// 指向链表头节点的指针transientNode<E>last;// 指向链表尾节点的指针}属性 作用 first头节点指针:若链表为空, first = null;否则指向第一个节点(prev = null)。last尾节点指针:若链表为空, last = null;否则指向最后一个节点(next = null)。size记录节点总数,避免遍历计数(O (1) 获取长度)。 结构示意:
null ← [prev|item1|next] ↔ [prev|item2|next] ↔ [prev|item3|next] → null ↑(first) ↑(last)- 头节点:
prev = null,next指向第二个节点; - 尾节点:
next = null,prev指向倒数第二个节点; - 中间节点:
prev指向前一个,next指向后一个; - 空链表:
first = last = null,size = 0。
构造方法(两种):
1.无参构造:
List<String>list=newLinkedList<>();2.基于集合构造:
List<String>list=newLinkedList<>(Arrays.asList("a","b","c"));- 头节点:
核心特点:
- 随机访问慢(需遍历链表,时间复杂度 O (n));
- 首尾插入 / 删除快(无需移动元素,时间复杂度 O (1));
- 非线程安全;
- 实现了
Deque接口,可作为队列 / 双端队列使用; - 适合写多读少、频繁增删的场景。
使用示例:
List<String>linkedList=newLinkedList<>();linkedList.addFirst("A");// 头部添加linkedList.addLast("B");// 尾部添加linkedList.removeFirst();// 删除头部3. Vector(古老的线程安全实现)
底层实现:基于数组,与 ArrayList 类似,但方法加了 synchronized 锁。
补充:
Vector 的本质是线程安全的动态扩容数组,与 ArrayList 一样,所有元素存储在 Object 数组中,核心区别是:Vector 的核心方法通过
synchronized修饰实现线程安全,扩容倍数和初始化逻辑也不同。publicclassVector<E>extendsAbstractList<E>implementsList<E>{// 存储元素的底层数组(与 ArrayList 的 elementData 一致)protectedObject[]elementData;// 集合中实际元素个数(与 ArrayList 的 size 一致)protectedintelementCount;// 扩容增量(可选:指定扩容时每次增加的容量,默认0)protectedintcapacityIncrement;// 序列化版本号(ArrayList 也有,仅序列化用)privatestaticfinallongserialVersionUID=-2767605614048989439L;}属性 作用 与 ArrayList 对比 elementData核心存储数组,类型为 Object [] 完全一致(ArrayList 用 transient 修饰,Vector 用 protected 修饰) elementCount实际元素个数(等价于 ArrayList 的 size) 命名不同,功能完全一致 capacityIncrement扩容增量:1. 若 > 0,扩容时数组长度 += 该值;2. 若 ≤ 0,扩容时数组长度翻倍(默认 0) ArrayList 无此属性,固定扩容 1.5 倍 DEFAULT_CAPACITY默认初始容量(值为 10) 与 ArrayList 一致,但初始化逻辑不同 构造方法(四种):
1.无参构造
Vector<String>vector=newVector<>();2.指定初始容量构造
Vector<String>vector=newVector<>(20);3.指定初始容量 + 扩容增量构造
Vector<String>vector=newVector<>(20,5);4.基于集合构造
Vector<String>vector=newVector<>(Arrays.asList("a","b","c"));扩容:
调用
add()/addAll()时,若elementCount + 新增元素数 > elementData.length,触发扩容。// 1. 计算新容量intnewCapacity=oldCapacity+((capacityIncrement>0)?capacityIncrement:oldCapacity);// 2. 若新容量不足(如新增大量元素),直接扩容至所需最小容量if(newCapacity-minCapacity<0)newCapacity=minCapacity;// 3. 拷贝原数组元素到新数组(与 ArrayList 一致,用 Arrays.copyOf)elementData=Arrays.copyOf(elementData,newCapacity);/* 扩容倍数规则: 若 capacityIncrement > 0:扩容后容量 = 原容量 + capacityIncrement(如初始 20、增量 5,扩容后 25); 若 capacityIncrement ≤ 0:扩容后容量 = 原容量 × 2(默认翻倍,ArrayList 是 1.5 倍); 扩容开销:与 ArrayList 一致,扩容需拷贝数组(O (n) 操作),且翻倍扩容比 1.5 倍更易造成内存浪费; 示例: 无参构造的 Vector(初始 10,增量 0):首次扩容至 20,第二次至 40,第三次至 80... 指定增量 5 的 Vector(初始 20):首次扩容至 25,第二次至 30,第三次至 35... */核心特点:
- 线程安全(但锁粒度大,性能差);
- 扩容默认 2 倍(ArrayList 1.5 倍);
- 基本被
CopyOnWriteArrayList替代,仅兼容旧代码时使用。
4. CopyOnWriteArrayList(并发安全)
底层实现:基于写时复制数组(修改操作会复制一份新数组,修改后替换原数组)。
补充:
CopyOnWriteArrayList 的本质是“只读原数组 + 写时复制新数组”的动态数组结构,核心设计思想:
- 读操作直接访问「不可变的原数组」,无需加锁,性能极高;
- 写操作(增 / 删 / 改)先复制一份新数组,在新数组上完成修改,再替换原数组,全程加锁保证线程安全;
- 所有数组操作均基于「不可变数组」,天然避免了并发修改异常(
ConcurrentModificationException)。
publicclassCopyOnWriteArrayList<E>implementsList<E>,RandomAccess{// 全局锁:仅用于写操作(add/remove/set),读操作无锁finaltransientReentrantLocklock=newReentrantLock();// 存储元素的底层数组(volatile 修饰,保证多线程可见性)privatetransientvolatileObject[]array;// 获取当前数组(核心内部方法)finalObject[]getArray(){returnarray;}// 替换数组(仅写操作后调用)finalvoidsetArray(Object[]a){array=a;}}属性 作用 核心设计考量 lock可重入锁(ReentrantLock) 仅锁定写操作,保证同一时间只有一个线程执行 “复制数组 + 修改”,避免多线程写冲突;读操作完全无锁。 array存储元素的核心数组(volatile 修饰) 1. volatile保证数组引用的可见性:修改后新数组的引用能立即被其他线程感知;2. 数组本身是 “不可变” 的:读操作期间数组不会被修改,无需担心并发问题。构造方法:
1.无参构造
CopyOnWriteArrayList<String>cowList=newCopyOnWriteArrayList<>();2.基于集合构造
CopyOnWriteArrayList<String>cowList=newCopyOnWriteArrayList<>(Arrays.asList("a","b","c"));写时复制原理:
1.读操作(无锁 + O (1) 高效)
读操作(
get()/iterator()/contains()等)全程无锁,直接访问当前array数组,无需担心并发修改:// get(int index):无锁,直接索引访问publicEget(intindex){returnelementAt(getArray(),index);// getArray() 返回当前 array,elementAt 等价于 (E) array[index]}// 迭代器:基于当前数组创建“快照迭代器”,遍历期间原数组修改不影响迭代结果publicIterator<E>iterator(){returnnewCOWIterator<>(getArray(),0);// 传入当前数组的副本,迭代器仅遍历该副本}- 核心优势:读操作无锁、无阻塞,即使写操作正在执行,读操作仍访问旧数组,不会抛出
ConcurrentModificationException; - 小缺点:读操作可能读取到 “旧数据”(最终一致性),适合对数据实时性要求不高、读多写少的场景(如配置缓存)。
2.写操作(加锁 + 复制数组 + O (n) 开销)
所有写操作(
add()/remove()/set())都遵循「加锁 → 复制原数组 → 修改新数组 → 替换原数组 → 解锁」的流程,核心是 “不修改原数组,仅修改副本”。写操作中的部分细节介绍:
- 锁粒度:仅写操作加锁,读操作不受影响,相比 Vector 的 “方法级 synchronized”,并发性能提升极大;
- 数组复制开销:写操作需拷贝整个数组(O (n) 时间复杂度),写频率越高,性能越差,因此仅适合 “读多写少” 场景;
- 内存占用:写操作期间会同时存在原数组和新数组两份内存,内存开销较大(如原数组 100 万元素,写时需额外占用 100 万元素的内存);
- 数据一致性:写操作的结果需等
setArray()后才对其他线程可见,读操作可能读取到修改前的旧数据(最终一致性,非强一致性)。
(1)新增操作(add (E e)):
publicbooleanadd(Ee){finalReentrantLocklock=this.lock;lock.lock();// 1. 加锁,独占写操作try{Object[]elements=getArray();// 2. 获取当前原数组intlen=elements.length;Object[]newElements=Arrays.copyOf(elements,len+1);// 3. 复制新数组(长度+1)newElements[len]=e;// 4. 在新数组尾部添加元素setArray(newElements);// 5. 替换原数组(volatile 保证可见性)returntrue;}finally{lock.unlock();// 6. 解锁,释放锁}}(2)删除操作(remove (int index)):
publicEremove(intindex){finalReentrantLocklock=this.lock;lock.lock();try{Object[]elements=getArray();intlen=elements.length;EoldValue=elementAt(elements,index);// 暂存被删除元素intnumMoved=len-index-1;Object[]newElements;if(numMoved==0){// 删除最后一个元素:复制长度-1的新数组newElements=Arrays.copyOf(elements,len-1);}else{// 删除中间元素:分两段拷贝(前半段 + 后半段)newElements=newObject[len-1];System.arraycopy(elements,0,newElements,0,index);System.arraycopy(elements,index+1,newElements,index,numMoved);}setArray(newElements);// 替换原数组returnoldValue;}finally{lock.unlock();}}(3)修改操作(set (int index, E element)):
publicEset(intindex,Eelement){finalReentrantLocklock=this.lock;lock.lock();try{Object[]elements=getArray();EoldValue=elementAt(elements,index);if(oldValue!=element){// 元素不同才复制修改intlen=elements.length;Object[]newElements=Arrays.copyOf(elements,len);// 复制等长新数组newElements[index]=element;// 修改新数组指定位置setArray(newElements);}else{// 元素相同,无需修改,直接返回(避免无意义的数组复制)setArray(elements);}returnoldValue;}finally{lock.unlock();}}核心特点:
- 线程安全(读操作无锁,写操作加锁但不阻塞读);
- 读性能极高,写性能较低(复制数组开销大);
- 迭代器是 “快照”,不会抛出
ConcurrentModificationException; - 适合读多写少的并发场景(如配置缓存、日志收集)。
使用示例:
List<String>cowList=newCopyOnWriteArrayList<>();cowList.add("a");cowList.add("b");// 多线程遍历无需加锁for(Strings:cowList){System.out.println(s);}四、List 常见使用场景
| 场景 | 推荐实现类 |
|---|---|
| 日常开发、读多写少、随机访问 | ArrayList |
| 频繁增删(尤其是首尾)、队列 / 栈场景 | LinkedList |
| 多线程并发、读多写少 | CopyOnWriteArrayList |
| 旧代码兼容、低并发线程安全 | Vector |
五、注意事项
subList 陷阱:
subList()返回的是原列表的视图,修改子列表会同步修改原列表,且子列表生命周期依赖原列表(原列表结构修改后,子列表操作会抛异常)。
List<Integer>list=newArrayList<>(Arrays.asList(1,2,3));List<Integer>sub=list.subList(0,2);sub.add(4);// 原列表变为 [1,2,4,3]list.clear();// 原列表清空后,sub.get(0) 会抛异常迭代器遍历:
遍历 List 时,若需删除元素,需使用ListIterator的remove()方法,而非for循环(否则抛ConcurrentModificationException)。
List<String>list=newArrayList<>(Arrays.asList("a","b","c"));ListIterator<String>it=list.listIterator();while(it.hasNext()){if(it.next().equals("b")){it.remove();// 安全删除}}空值处理:List 允许存储 null,但实际开发中应尽量避免(容易导致
NullPointerException,且部分场景如排序会报错)。线程安全:ArrayList/LinkedList 非线程安全,多线程修改时需手动加锁(如
Collections.synchronizedList()),或直接使用 CopyOnWriteArrayList。
六、总结
List 是 Java 中有序可重复的集合,核心实现类各有侧重:
- ArrayList:性能均衡,适合大多数场景;
- LinkedList:适合频繁增删的场景;
- CopyOnWriteArrayList:适合并发读多写少的场景;
- Vector:尽量避免使用,仅兼容旧代码。
选择时需根据 “访问效率、修改频率、并发场景” 三大维度权衡。
附表:
| JDK 版本 | List 接口核心变更 | ArrayList 关键变更 | LinkedList 关键变更 | Vector 关键变更 | CopyOnWriteArrayList 关键变更 |
|---|---|---|---|---|---|
| JDK 1.2 | 1. 正式引入List接口,继承Collection,定义核心索引方法(get/set/add(int, E)/remove(int)等);2. 同时引入ArrayList、LinkedList、Vector实现类;3.Vector作为早期线程安全实现,方法已加synchronized。 | 1. 初始版本基于动态数组实现,默认初始容量 10;2. 扩容逻辑为「按需扩容」,早期扩容倍数不固定(后续 JDK 1.4 定型为 1.5 倍);3. 无trimToSize()之外的内存优化方法。 | 1. 基于双向链表实现,实现List接口但未实现Deque;2. 仅提供基础的增删改查方法,无addFirst/addLast等队列方法(后续 JDK 1.6 补充)。 | 1. 初始版本即线程安全(方法加synchronized);2. 扩容默认翻倍(capacityIncrement默认为 0);3. 核心属性elementData为protected修饰。 | 未引入(属于并发包,JDK 1.5 才加入) |
| JDK 1.3 | 无接口层面变更,仅优化集合框架的内部迭代器实现,减少ConcurrentModificationException触发概率。 | 优化数组拷贝逻辑,引入System.arraycopy替代原生循环拷贝,提升扩容性能。 | 优化链表节点的内存布局,减少空指针异常风险;新增indexOf/lastIndexOf方法的遍历优化。 | 无核心变更,仅修复少量线程安全漏洞(如扩容时的竞态条件)。 | 未引入 |
| JDK 1.4 | 1. 新增ListIterator接口,支持双向遍历、添加 / 修改元素;2.List接口新增listIterator()方法;3. 完善subList()方法规范,明确子列表为原列表视图。 | 1. 定型扩容逻辑:默认扩容 1.5 倍(oldCapacity + (oldCapacity >> 1));2. 新增ensureCapacity(int)方法,支持手动预扩容;3. 修复subList视图修改导致的数组越界问题。 | 1. 实现ListIterator接口,支持双向迭代;2. 优化链表遍历性能,减少节点指针遍历开销。 | 1. 新增isEmpty()方法的优化(直接判断elementCount == 0);2. 对齐ArrayList的trimToSize()方法。 | 未引入 |
| JDK 1.5 | 1. 引入泛型(Generic),List接口支持类型参数(List<E>),解决原始类型的类型安全问题;2. 新增Collections.synchronizedList(List)工具方法,为非线程安全 List 提供线程安全包装。 | 1. 全面支持泛型,替换所有Object强转,避免类型转换异常;2. 优化空列表初始化(引入空数组常量,减少内存占用)。 | 1. 支持泛型,节点Node改为泛型类型(Node<E>);2. 新增offer/peek/poll等队列方法(初步兼容队列特性)。 | 1. 支持泛型;2. 性能优化:减少synchronized锁的不必要开销。 | 1. 首次引入(位于java.util.concurrent包);2. 基于写时复制(COW)实现,支持基本的增删改查;3. 初始版本已实现RandomAccess接口,支持随机访问。 |
| JDK 1.6 | 1.List接口新增toArray(T[] a)重载方法,支持指定类型数组返回;2.LinkedList实现Deque接口,扩展队列 / 双端队列能力。 | 1. 优化addAll方法的扩容逻辑,减少多次扩容;2. 修复迭代器遍历期间的并发修改异常(fast-fail机制优化)。 | 1. 实现Deque接口,新增addFirst/addLast/removeFirst/removeLast等方法;2. 优化链表首尾操作性能(直接通过first/last指针访问,无需遍历)。 | 1. 新增copyInto(Object[])方法的重载;2. 标记为「低效线程安全实现」,官方推荐优先使用CopyOnWriteArrayList。 | 1. 优化写操作的锁粒度(使用ReentrantLock替代synchronized);2. 新增iterator()快照迭代器,避免并发修改异常。 |
| JDK 1.7 | 无接口层面核心变更,仅优化集合框架的内存占用。 | 1. 移除elementData中的多余空数组常量,减少内存浪费;2. 优化trimToSize()方法,避免不必要的数组拷贝。 | 优化链表节点的创建逻辑,减少对象创建开销;修复空链表操作时的空指针异常。 | 无核心变更,仅维护性修复。 | 1. 优化数组复制逻辑,使用Arrays.copyOf替代手动循环;2. 修复写操作时的内存溢出问题(新增MAX_ARRAY_SIZE限制)。 |
| JDK 1.8 | 1. 引入 Stream API,List可通过stream()/parallelStream()实现流式操作;2. 新增forEach(Consumer)方法,支持函数式遍历;3. 新增replaceAll(UnaryOperator)、sort(Comparator)方法。 | 1. 实现forEach/replaceAll/sort方法,适配函数式编程;2. 优化stream()遍历性能,减少迭代器创建开销;3. 无参构造时,elementData初始化为空数组(延迟扩容至首次add时的 10)。 | 1. 适配 Stream API 和函数式方法;2. 优化sort方法,先转为数组排序再重建链表(提升排序性能)。 | 1. 适配函数式方法(forEach/replaceAll);2. 官方文档明确标注「过时推荐使用 CopyOnWriteArrayList」。 | 1. 适配 Stream API 和函数式遍历;2. 优化读操作的可见性(array数组保持volatile修饰);3. 减少写操作时的无意义数组复制(如set方法判断元素是否相同)。 |
| JDK 9 | 1. 新增List.of()静态工厂方法,创建不可变 List(元素不可增删改,不支持 null);2. 新增List.copyOf()方法,复制现有集合为不可变 List。 | 无核心实现变更,仅兼容List.copyOf()方法(返回不可变 ArrayList 视图)。 | 无核心变更,List.copyOf()对 LinkedList 复制时转为不可变数组实现。 | 无核心变更,List.copyOf()不推荐用于 Vector。 | 1. 兼容List.copyOf()方法,返回不可变副本;2. 优化锁的公平性,减少写操作的锁竞争。 |
| JDK 10+ | 1. 不可变 List 进一步优化(减少内存占用,提升遍历性能);2. 修复subList()视图在并发场景下的异常。 | 1. 优化内存布局,使用压缩指针减少elementData数组的内存开销;2. 修复扩容时的整数溢出问题(hugeCapacity方法优化)。 | 优化链表节点的 GC 回收效率,减少内存泄漏风险。 | 仅维护性修复,无功能增强。 | 1. 优化写操作的数组复制性能(引入 JVM 层面的数组拷贝优化);2. 修复迭代器快照的一致性问题。 |