List 接口
目录
文档链接:
一、List 接口的核心特性
1. 允许重复元素
2. 有序性与索引访问
3. 扩展的迭代器:ListIterator
4. 位置相关的操作
二、ListIterator
三、不可修改列表:List.of 与 List.copyOf
1. List.of 的使用
2. List.copyOf 的使用
四、List 接口常用方法
1. 元素访问方法
2. 添加元素方法
3. 删除元素方法
4. 修改元素方法
5. 搜索与比较方法
6. 视图与迭代方法
五、注意事项
1、选择合适的实现类
2、避免在循环中使用索引访问 LinkedList
3、谨慎使用线性搜索方法
4、不可修改列表的使用场景
5、避免列表包含自身作为元素
在 Java 集合框架中,List 接口是最常用的接口之一,它继承自 Collection 接口,为元素的有序存储和访问提供了丰富的功能。与 Set 等接口不同,List 允许重复元素,支持基于索引的访问。
文档链接:
点击此处,List文档链接
一、List 接口的核心特性
List 接口作为 Collection 的子接口,在保留 Collection 基本功能的基础上,增加了许多针对 "有序集合" 的特性,主要包括:
1. 允许重复元素
与 Set 接口不同,List 允许存在多个相等的元素(即e1.equals(e2)
为true
的元素)。例如,一个List<String>
可以同时包含多个 "hello" 字符串;如果实现类允许 null 元素,List 还支持多个 null 值共存。
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); // 允许多个nullSystem.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 进行双向遍历与修改
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");// 获取ListIteratorListIterator<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 个元素的列表,以及通过可变参数创建任意长度的列表:
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
用于基于已有集合创建不可修改列表,其元素顺序与原集合一致:
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)
将列表转为指定类型的数组
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 的所有元素插入到指定索引
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插入15System.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,6System.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()
清空列表所有元素
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)
根据比较器对列表排序
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为10System.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()
返回列表的哈希码(基于元素顺序和值)
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")); // trueSystem.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()
返回可分割的迭代器(用于并行处理)
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 // 从指定索引开始的ListIteratorSystem.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 循环遍历:
// 不推荐: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
方法无法正确计算(递归无限循环),应尽量避免这种用法。