目录
[一、List 接口的核心特性](#一、List 接口的核心特性)
[1. 允许重复元素](#1. 允许重复元素)
[2. 有序性与索引访问](#2. 有序性与索引访问)
[3. 扩展的迭代器:ListIterator](#3. 扩展的迭代器:ListIterator)
[4. 位置相关的操作](#4. 位置相关的操作)
[三、不可修改列表:List.of 与 List.copyOf](#三、不可修改列表:List.of 与 List.copyOf)
[1. List.of 的使用](#1. List.of 的使用)
[2. List.copyOf 的使用](#2. List.copyOf 的使用)
[四、List 接口常用方法](#四、List 接口常用方法)
[1. 元素访问方法](#1. 元素访问方法)
[2. 添加元素方法](#2. 添加元素方法)
[3. 删除元素方法](#3. 删除元素方法)
[4. 修改元素方法](#4. 修改元素方法)
[5. 搜索与比较方法](#5. 搜索与比较方法)
[6. 视图与迭代方法](#6. 视图与迭代方法)
[2、避免在循环中使用索引访问 LinkedList](#2、避免在循环中使用索引访问 LinkedList)
在 Java 集合框架中,List 接口是最常用的接口之一,它继承自 Collection 接口,为元素的有序存储和访问提供了丰富的功能。与 Set 等接口不同,List 允许重复元素,支持基于索引的访问。
文档链接:
一、List 接口的核心特性
List 接口作为Collection的子接口,在保留 Collection 基本功能的基础上,增加了许多针对 "有序集合" 的特性,主要包括:
1. 允许重复元素
与 Set接口不同,List允许存在多个相等的元素(即**e1.equals(e2)
为true
的元素)。例如,一个List<String>
可以同时包含多个 "hello" 字符串;如果实现类允许null** 元素,List 还支持**++多个++** null 值共存。
java
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("hello"); // 允许重复元素
list.add(null);
list.add(null); // 允许多个null
System.out.println(list); // 输出:[hello, hello, null, null]
}
}
2. 有序性与索引访问
List 中的元素具有++明确++的顺序(即插入顺序),且支持基于索引(从零开始)的访问,类似数组。这意味着我们可以通过索引直接获取、修改、插入或删除元素。
3. 扩展的迭代器:ListIterator
List 提供了专门的迭代器++ListIterator
++ ,相比普通的++Iterator
++ ,它支持双向遍历 (向前 / 向后移动)、元素插入 、元素替换等功能,更适合 List 的特性。
4. 位置相关的操作
List 新增了大量与位置相关的方法,例如 get(int index)
(获取指定索引元素)、add(int index, E element)
(指定位置插入)、remove(int index)
(删除指定索引元素)等,这些方法是 List 区别于其他 Collection 子接口的核心。
二、ListIterator
**ListIterator
**是 List 接口提供的特殊迭代器,它继承自 Iterator
,并扩展了更多功能。其核心方法包括:
hasNext()
:是否有下一个元素(正向遍历)next()
:获取下一个元素hasPrevious()
:是否有上一个元素(反向遍历)previous()
:获取上一个元素add(E e)
:在当前位置插入元素set(E e)
:替换当前迭代的元素remove()
:删除当前迭代的元素
代码示例:使用 ListIterator 进行双向遍历与修改
java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListIteratorDemo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("banana");
fruits.add("cherry");
// 获取ListIterator
ListIterator<String> iterator = fruits.listIterator();
// 正向遍历并修改元素
System.out.println("正向遍历:");
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.print(fruit + " ");
if (fruit.equals("banana")) {
iterator.set("grape"); // 将banana替换为grape
}
}
// 输出:正向遍历:apple banana cherry
// 反向遍历并插入元素
System.out.println("\n反向遍历:");
while (iterator.hasPrevious()) {
String fruit = iterator.previous();
System.out.print(fruit + " ");
if (fruit.equals("apple")) {
iterator.add("orange"); // 在apple前插入orange
}
}
// 输出:反向遍历:grape apple
System.out.println("\n最终列表:" + fruits);
// 输出:最终列表:[orange, apple, grape, cherry]
}
}
从示例可见,
ListIterator
不仅能双向遍历,还能在遍历过程中修改列表,这比普通Iterator
更灵活,但也需要注意:
遍历过程中若通过 List 的方法(如 add/remove)修改列表,会导致迭代器抛出
ConcurrentModificationException
,因此建议通过迭代器自身的方法修改元素。
三、不可修改列表:List.of 与 List.copyOf
Java 9 引入了List.of
和List.copyOf
两个静态工厂方法,用于创建不可修改的列表。这类列表具有以下特性:
- 不可修改 :调用
add
、remove
、set
等修改方法会抛出UnsupportedOperationException
;- 不允许 null 元素 :创建时包含 null 会抛出
NullPointerException
;- 值传递:若元素本身可变,列表内容可能间接变化(但列表结构不变);
- 支持序列化:当所有元素可序列化时,列表可序列化。
1. List.of 的使用
**List.of
**提供了多个重载方法,支持创建包含++0 到 10 个元素++的列表,以及通过可变参数创建任意长度的列表:
java
import java.util.List;
public class ListOfDemo {
public static void main(String[] args) {
// 创建空列表
List<String> emptyList = List.of();
// 创建包含1个元素的列表
List<Integer> singleList = List.of(100);
// 创建包含3个元素的列表
List<Double> tripleList = List.of(1.1, 2.2, 3.3);
// 通过可变参数创建列表
List<String> varArgsList = List.of("a", "b", "c", "d");
// 尝试修改会抛出异常
try {
varArgsList.add("e");
} catch (UnsupportedOperationException e) {
System.out.println("不可修改列表:" + e.getMessage());
}
// 尝试添加null会抛出异常
try {
List.of(null);
} catch (NullPointerException e) {
System.out.println("不允许null元素:" + e.getMessage());
}
}
}
2. List.copyOf 的使用
List.copyOf
用于基于已有集合创建不可修改列表,其元素顺序与原集合一致:
java
import java.util.ArrayList;
import java.util.List;
public class ListCopyOfDemo {
public static void main(String[] args) {
List<String> original = new ArrayList<>();
original.add("java");
original.add("python");
// 基于original创建不可修改列表
List<String> copy = List.copyOf(original);
// 原列表修改不影响copy(copy是快照)
original.add("c++");
System.out.println("原列表:" + original); // [java, python, c++]
System.out.println("copy列表:" + copy); // [java, python]
// 尝试修改copy抛出异常
try {
copy.remove(0);
} catch (UnsupportedOperationException e) {
System.out.println("copy列表不可修改:" + e.getMessage());
}
}
}
注意 :
List.copyOf
会对原集合进行 "快照",原集合后续的修改不会影响复制后的列表; 若原集合包含 null 元素,List.copyOf
会抛出**NullPointerException
**。
四、List 接口常用方法
List接口提供了丰富的方法,可分为元素访问 、添加元素 、删除元素 、修改元素 、搜索与比较 、批量操作 等类别。以下是常用方法的详细说明及代码示例(以ArrayList
为例,它实现了 List 的所有可选操作)。
1. 元素访问方法
方法 描述 E get(int index)
返回指定索引的元素 int size()
返回列表元素个数 boolean isEmpty()
判断列表是否为空 Object[] toArray()
将列表转为数组(返回 Object []) <T> T[] toArray(T[] a)
将列表转为指定类型的数组
java
import java.util.ArrayList;
import java.util.List;
public class ElementAccessDemo {
public static void main(String[] args) {
List<String> colors = new ArrayList<>();
colors.add("red");
colors.add("green");
colors.add("blue");
// 获取指定索引元素
System.out.println("索引1的元素:" + colors.get(1)); // green
// 获取元素个数
System.out.println("元素个数:" + colors.size()); // 3
// 判断是否为空
System.out.println("是否为空:" + colors.isEmpty()); // false
// 转为Object数组
Object[] objArray = colors.toArray();
System.out.println("Object数组:" + objArray[0]); // red
// 转为指定类型数组
String[] strArray = colors.toArray(new String[0]);
System.out.println("String数组:" + strArray[2]); // blue
}
}
2. 添加元素方法
方法 描述 boolean add(E e)
在列表末尾添加元素(返回是否成功) void add(int index, E element)
在指定索引插入元素(后续元素后移) boolean addAll(Collection<? extends E> c)
将集合 c 的所有元素添加到列表末尾 boolean addAll(int index, Collection<? extends E> c)
将集合 c 的所有元素插入到指定索引
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AddElementDemo {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
// 末尾添加元素
numbers.add(10);
numbers.add(20);
System.out.println("添加后:" + numbers); // [10, 20]
// 指定位置插入元素
numbers.add(1, 15); // 在索引1插入15
System.out.println("插入后:" + numbers); // [10, 15, 20]
// 添加另一个集合的所有元素(末尾)
List<Integer> moreNumbers = Arrays.asList(30, 40);
numbers.addAll(moreNumbers);
System.out.println("批量添加后:" + numbers); // [10, 15, 20, 30, 40]
// 插入另一个集合的所有元素(指定位置)
List<Integer> prefix = Arrays.asList(5, 6);
numbers.addAll(0, prefix); // 在索引0插入5,6
System.out.println("指定位置批量插入后:" + numbers); // [5, 6, 10, 15, 20, 30, 40]
}
}
注意:
add(int index, E)
和addAll(int index, ...)
会导致索引index
及之后的元素后移,对于LinkedList
(链表实现),这种操作效率较高;但对于ArrayList
(数组实现),可能需要复制数组,索引越大效率越低。
3. 删除元素方法
方法 描述 E remove(int index)
删除指定索引的元素,返回被删除的元素 boolean remove(Object o)
删除第一个与 o 相等的元素(返回是否删除成功) boolean removeAll(Collection<?> c)
删除列表中所有在集合 c 中的元素(差集) boolean retainAll(Collection<?> c)
保留列表中所有在集合 c 中的元素(交集) void clear()
清空列表所有元素
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class RemoveElementDemo {
public static void main(String[] args) {
List<String> languages = new ArrayList<>(Arrays.asList("java", "python", "c", "java", "go"));
// 删除指定索引元素
String removed = languages.remove(2); // 删除索引2的"c"
System.out.println("删除的元素:" + removed + ",剩余:" + languages); // [java, python, java, go]
// 删除第一个匹配的元素
boolean isRemoved = languages.remove("java"); // 删除第一个"java"
System.out.println("是否删除成功:" + isRemoved + ",剩余:" + languages); // [python, java, go]
// 删除与集合c交集的元素(removeAll)
List<String> toRemove = Arrays.asList("python", "c++");
languages.removeAll(toRemove);
System.out.println("removeAll后:" + languages); // [java, go]
// 保留与集合c交集的元素(retainAll)
List<String> toRetain = Arrays.asList("java", "python");
languages.retainAll(toRetain);
System.out.println("retainAll后:" + languages); // [java]
// 清空列表
languages.clear();
System.out.println("清空后:" + languages); // []
}
}
4. 修改元素方法
方法 描述 E set(int index, E element)
替换指定索引的元素,返回被替换的元素 default void replaceAll(UnaryOperator<E> operator)
用函数运算结果替换所有元素 default void sort(Comparator<? super E> c)
根据比较器对列表排序
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.UnaryOperator;
public class ModifyElementDemo {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5));
// 替换指定索引元素
Integer old = nums.set(2, 10); // 替换索引2的4为10
System.out.println("被替换的元素:" + old + ",替换后:" + nums); // [3, 1, 10, 1, 5]
// 批量替换(每个元素乘2)
UnaryOperator<Integer> doubleOp = n -> n * 2;
nums.replaceAll(doubleOp);
System.out.println("批量替换后:" + nums); // [6, 2, 20, 2, 10]
// 排序(默认升序)
nums.sort(Comparator.naturalOrder());
System.out.println("升序排序后:" + nums); // [2, 2, 6, 10, 20]
// 排序(降序)
nums.sort(Comparator.reverseOrder());
System.out.println("降序排序后:" + nums); // [20, 10, 6, 2, 2]
}
}
5. 搜索与比较方法
方法 描述 int indexOf(Object o)
返回元素 o 第一次出现的索引(不存在返回 - 1) int lastIndexOf(Object o)
返回元素 o 最后一次出现的索引(不存在返回 - 1) boolean contains(Object o)
判断列表是否包含元素 o boolean containsAll(Collection<?> c)
判断列表是否包含集合 c 的所有元素 boolean equals(Object o)
判断与另一个对象是否相等(列表需元素顺序和值均相同) int hashCode()
返回列表的哈希码(基于元素顺序和值)
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class SearchCompareDemo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>(Arrays.asList("apple", "banana", "apple", "orange"));
// 查找第一次出现的索引
System.out.println("apple第一次出现的索引:" + fruits.indexOf("apple")); // 0
// 查找最后一次出现的索引
System.out.println("apple最后一次出现的索引:" + fruits.lastIndexOf("apple")); // 2
// 判断是否包含元素
System.out.println("是否包含banana:" + fruits.contains("banana")); // true
System.out.println("是否包含grape:" + fruits.contains("grape")); // false
// 判断是否包含集合所有元素
List<String> subset = Arrays.asList("apple", "banana");
System.out.println("是否包含subset所有元素:" + fruits.containsAll(subset)); // true
// 比较两个列表是否相等(顺序和元素均需相同)
List<String> another = new ArrayList<>(Arrays.asList("apple", "banana", "apple", "orange"));
System.out.println("两个列表是否相等:" + fruits.equals(another)); // true
// 哈希码(相同元素和顺序的列表哈希码相同)
System.out.println("fruits的哈希码:" + fruits.hashCode());
System.out.println("another的哈希码:" + another.hashCode()); // 与fruits相同
}
}
注意:
indexOf
和 lastIndexOf通过***
equals
*** 方法判断元素相等,因此若元素重写了equals
,需确保逻辑正确;此外,这两个方法是线性搜索(时间复杂度 O (n)),对于大型列表,频繁调用可能影响性能。
6. 视图与迭代方法
方法 描述 List<E> subList(int fromIndex, int toIndex)
返回从 fromIndex(含)到 toIndex(不含)的子列表视图 Iterator<E> iterator()
返回普通迭代器(正向遍历) ListIterator<E> listIterator()
返回 ListIterator(双向遍历) ListIterator<E> listIterator(int index)
从指定索引开始的 ListIterator default Spliterator<E> spliterator()
返回可分割的迭代器(用于并行处理)
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
public class ViewIteratorDemo {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6));
// 获取子列表(视图,修改会影响原列表)
List<Integer> subList = numbers.subList(1, 4); // 索引1到3(元素2,3,4)
System.out.println("子列表:" + subList); // [2, 3, 4]
// 修改子列表,原列表也会变化
subList.set(0, 20);
System.out.println("修改子列表后,原列表:" + numbers); // [1, 20, 3, 4, 5, 6]
// 普通迭代器(正向遍历)
System.out.print("普通迭代器遍历:");
for (Integer num : numbers) { // 底层使用iterator()
System.out.print(num + " ");
}
// 输出:1 20 3 4 5 6
// 从指定索引开始的ListIterator
System.out.print("\n从索引3开始的ListIterator:");
ListIterator<Integer> lit = numbers.listIterator(3);
while (lit.hasNext()) {
System.out.print(lit.next() + " ");
}
// 输出:4 5 6
}
}
注意:
subList
返回的是原列表的视图 ,而非新列表。对子列表的修改(如添加、删除、替换)会直接影响原列表,且原列表的结构修改(如添加 / 删除元素)会导致子列表抛出ConcurrentModificationException
。
五、注意事项
1、选择合适的实现类
List 的常用实现类有**ArrayList
**(数组实现)和 **LinkedList
(**链表实现):
ArrayList
:随机访问快(get
/set
效率高),但插入 / 删除中间元素效率低(需移动元素);LinkedList
:插入 / 删除中间元素快,但随机访问效率低(需遍历链表)。- 若频繁访问元素,优先选
ArrayList
;若频繁插入 / 删除中间元素,可考虑LinkedList
。
2、避免在循环中使用索引访问 LinkedList
对于 LinkedList
, get(index)
方法需要++从表头 / 表尾遍++ 历到指定索引,时间复杂度为 O (n)。若在循环中多次调用 get(index)
,总时间复杂度会变为 O (n²),严重影响性能。建议使用迭代器或增强 for 循环遍历:
java
// 不推荐:LinkedList循环中使用get(index)
List<String> linkedList = new LinkedList<>();
for (int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i)); // 效率低
}
// 推荐:使用增强for循环(底层是迭代器)
for (String s : linkedList) {
System.out.println(s); // 效率高
}
3、谨慎使用线性搜索方法
indexOf
、lastIndexOf
、contains
等方法均为线性搜索(O (n)),对于大型列表(如 10 万级元素),频繁调用会导致性能问题。若需频繁搜索,可考虑先将 List 转为HashSet
(搜索效率 O (1)),但需注意元素顺序和重复问题。
4、不可修改列表的使用场景
List.of
和**List.copyOf
**创建的不可修改列表适合用于:
- 存储常量数据(如配置项、枚举值);
- 作为方法返回值,防止外部修改内部列表;
- 多线程环境下,避免并发修改问题(无需额外同步)。
5、避免列表包含自身作为元素
虽然 List 允许包含自身作为元素,但会导致**equals
** 和 **hashCode
**方法无法正确计算(递归无限循环),应尽量避免这种用法。