使用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
,不能进行添加或删除操作。