使用List
约 1230 字大约 4 分钟
2025-07-18
List 是一种基础的集合类型,它代表一个有序列表。List 在内部按照元素的添加顺序存储,并且每个元素都可以通过索引来访问,索引从 0 开始,这与数组类似。
2.1 List 与数组的对比
虽然数组和 List 都是有序结构,但数组在添加和删除元素时不够灵活。例如,从数组中删除一个元素需要移动后续元素,而添加元素则需要先移动现有元素以腾出空间。这些操作在数组中实现起来比较麻烦。
因此,在需要频繁增删元素的场景下,ArrayList 是更常用的选择。ArrayList 内部使用数组存储元素,并封装了添加和删除的操作,使得操作 List 类似于操作数组,而无需关心内部元素的移动。
例如,一个 ArrayList 拥有 5 个元素,而内部数组的大小为 6,保留一个空位:
size=5
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ │
└───┴───┴───┴───┴───┴───┘当向 ArrayList 的指定索引添加元素时,ArrayList 会自动移动相应元素:
size=5
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘然后,将新元素插入到内部数组的指定位置,并将 size 加 1:
size=6
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘如果继续添加元素,且内部数组已满,ArrayList 会创建一个更大的新数组,并将旧数组的所有元素复制到新数组中,然后用新数组取代旧数组:
size=6
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘现在,新数组有了空位,可以继续在数组末尾添加元素,同时 size 加 1:
size=7
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │ G │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘总而言之,ArrayList 内部通过数组实现,并封装了数组的添加和删除操作,实现了动态数组的功能,使得在需要频繁增删元素的场景下,操作更加简便。
2.2 List 接口的主要方法
List<E> 接口提供了一系列方法来操作列表中的元素:
| 方法 | 描述 |
|---|---|
boolean add(E e) | 在列表末尾添加一个元素。 |
boolean add(int index, E e) | 在指定索引位置添加一个元素。 |
E remove(int index) | 删除指定索引位置的元素。 |
boolean remove(Object e) | 删除指定的元素。 |
E get(int index) | 获取指定索引位置的元素。 |
int size() | 获取列表的大小(包含元素的个数)。 |
2.3 ArrayList 和 LinkedList 的比较
ArrayList 和 LinkedList 都实现了 List 接口,但它们的内部实现方式不同。ArrayList 基于数组实现,而 LinkedList 基于链表实现。
ArrayList | LinkedList | |
|---|---|---|
| 获取指定元素 | 速度很快 | 需要从头开始查找元素 |
| 添加元素到末尾 | 速度很快 | 速度很快 |
| 在指定位置添加/删除 | 需要移动元素 | 不需要移动元素 |
| 内存占用 | 少 | 较大 |
通常情况下,优先使用 ArrayList。
2.4 List 的特点
- 允许重复元素:
List允许添加重复的元素。 - 允许
null值:List允许添加null值。
2.5 创建 List
除了使用 ArrayList 和 LinkedList 之外,还可以使用 List 接口提供的 of() 方法快速创建 List:
List<Integer> list = List.of(1, 2, 5);但是,List.of() 方法不接受 null 值,如果传入 null,会抛出 NullPointerException 异常。
2.6 遍历 List
遍历 List 有多种方式:
- 使用
for循环和get(int)方法: 虽然可以使用for循环和get(int)方法来遍历List,但这并不是推荐的方式,因为它在LinkedList中效率较低。 - 使用迭代器
Iterator: 通过迭代器Iterator遍历List是最高效的方式。import java.util.Iterator; import java.util.List; public class Main { public static void main(String[] args) { List<String> list = List.of("apple", "pear", "banana"); for (Iterator<String> it = list.iterator(); it.hasNext(); ) { String s = it.next(); System.out.println(s); } } } - 使用
for each循环:for each循环是使用迭代器Iterator的语法糖,可以简化代码。import java.util.List; public class Main { public static void main(String[] args) { List<String> list = List.of("apple", "pear", "banana"); for (String s : list) { System.out.println(s); } } }
2.7 List 和 Array 转换
List转换为Array:toArray(): 返回Object[]数组,会丢失类型信息,实际应用较少。toArray(T[]): 传入一个类型相同的数组,List内部自动把元素复制到传入的数组中。如果传入的数组不够大,那么List内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List元素还要多,那么填充完元素后,剩下的数组元素一律填充null。最常用的方式是传入一个“恰好”大小的数组:Integer[] array = list.toArray(new Integer[list.size()]);toArray(IntFunction<T[]> generator): 更简洁的写法,使用函数式接口。Integer[] array = list.toArray(Integer[]::new);
Array转换为List:List.of(T...): 将数组转换为List。Arrays.asList(T...): 在 JDK 11 之前的版本中使用。
需要注意的是,使用 List.of() 返回的是一个只读 List,不能进行添加或删除操作。