Jakob Jenkov 2020-02-29
Java 的 List 接口(java.util.List)表示一个有序的对象序列。List 中的元素可以根据其在列表内部的顺序进行插入、访问、遍历和删除。这种数据结构之所以被称为“列表”,正是因为其元素具有明确的顺序。
List 中的每个元素都有一个索引。第一个元素的索引为 0,第二个为 1,依此类推。这个索引表示“距离列表开头有多少个元素”。因此,第一个元素距离列表开头 0 个位置——因为它就在开头。
你可以向 List 中添加任意 Java 对象。如果该 List 没有使用 Java 泛型(Generics) 进行类型限定,则甚至可以在同一个 List 中混合不同类型的对象(类)。不过,在实践中很少会这样做。
Java 的 List 接口是一个标准的 Java 接口,并且它是 Java Collection 接口的子类型,也就是说 List 继承自 Collection。
List 与 Set 的区别
Java 的 List 接口与 Java Set 接口非常相似,因为它们都表示一组元素。但两者之间存在一些显著差异,这些差异也体现在各自接口提供的方法上。
- 重复性:
List允许同一个元素出现多次;而Set中每个元素只能出现一次。 - 顺序性:
List中的元素是有顺序的,可以按该顺序进行遍历;而Set不保证内部元素的任何顺序。
List 的实现类
由于 List 是一个接口,你需要实例化它的某个具体实现类才能使用。Java Collections API 提供了以下几种 List 实现:
java.util.ArrayListjava.util.LinkedListjava.util.Vectorjava.util.Stack
其中,ArrayList 是最常用的实现。
此外,在 java.util.concurrent 包中还提供了并发安全的 List 实现。
创建 List 实例
你可以通过实例化上述任一实现类来创建 List:
List listA = new ArrayList();
List listB = new LinkedList();
List listC = new Vector();
List listD = new Stack();
通常情况下,你会使用 ArrayList,但在某些特定场景下,其他实现可能更合适。
泛型 List(Generic Lists)
默认情况下,你可以向 List 中放入任何 Object。但从 Java 5 开始,借助 Java 泛型(Generics),你可以限制 List 中允许插入的对象类型。
示例:
List<MyObject> list = new ArrayList<MyObject>();
此 List 只能插入 MyObject 类型的实例。访问和遍历时无需强制类型转换:
List<MyObject> list = new ArrayList<MyObject>();
list.add(new MyObject("First MyObject"));
MyObject myObject = list.get(0);
for (MyObject anObject : list) {
// 对 anObject 执行操作...
}
如果不使用泛型,代码会变成这样:
List list = new ArrayList(); // 未指定泛型类型
list.add(new MyObject("First MyObject"));
MyObject myObject = (MyObject) list.get(0); // 需要强制转换
for (Object anObject : list) {
MyObject theMyObject = (MyObject) anObject; // 需要强制转换
// 对 anObject 执行操作...
}
可以看到,没有泛型时,每次从 List 中取出对象都需要显式转换为实际类型。
最佳实践:尽可能为 List 变量声明泛型类型。这有助于:
- 避免插入错误类型的对象;
- 无需强制转换即可安全地获取元素;
- 提高代码可读性,让读者清楚知道
List应包含什么类型。
除非有充分理由,否则不应省略泛型。
本文后续所有示例将尽可能使用泛型
List。
更多关于 Java 泛型的信息,请参阅 Java 泛型教程。
向 Java List 中插入元素
使用 add() 方法向 List 中插入元素:
List<String> listA = new ArrayList<>();
listA.add("element 1");
listA.add("element 2");
listA.add("element 3");
前三个 add() 调用会将 String 实例添加到列表末尾。
插入 null 值
Java List 允许插入 null 值:
Object element = null;
List<Object> list = new ArrayList<>();
list.add(element);
在指定索引处插入元素
List 接口提供了一个重载的 add(int index, E element) 方法,用于在指定位置插入元素:
list.add(0, "element 4");
如果列表中已有元素,原有元素会向后移动(索引 +1)。
将一个 List 的所有元素插入另一个 List
使用 addAll(Collection<? extends E> c) 方法:
List<String> listSource = new ArrayList<>();
listSource.add("123");
listSource.add("456");
List<String> listDest = new ArrayList<>();
listDest.addAll(listSource); // 将 source 中所有元素添加到 dest
注意:addAll() 接受 Collection 类型参数,因此你也可以传入 Set。
从 Java List 中获取元素
通过索引使用 get(int index) 方法获取元素:
List<String> listA = new ArrayList<>();
listA.add("element 0");
listA.add("element 1");
listA.add("element 2");
String element0 = listA.get(0);
String element1 = listA.get(1);
String element2 = listA.get(2);
也可以按内部存储顺序遍历整个列表(见后文“遍历 List”部分)。
在 List 中查找元素
查找首次出现的位置:indexOf()
List<String> list = new ArrayList<>();
String element1 = "element 1";
String element2 = "element 2";
list.add(element1);
list.add(element2);
int index1 = list.indexOf(element1); // 0
int index2 = list.indexOf(element2); // 1
查找最后一次出现的位置:lastIndexOf()
list.add(element1); // 再次添加 element1
int lastIndex = list.lastIndexOf(element1); // 返回 2
判断 List 是否包含某元素:contains()
boolean containsElement = list.contains("element 1"); // true
contains() 方法内部会遍历列表,并使用元素的 equals() 方法进行比较。
也可以检查是否包含 null:
list.add(null);
boolean hasNull = list.contains(null); // true
当参数为 null 时,contains() 使用 == 而非 equals() 进行比较。
从 Java List 中删除元素
按对象删除:remove(Object o)
List<String> list = new ArrayList<>();
String element = "first element";
list.add(element);
list.remove(element); // 删除该元素
按索引删除:remove(int index)
list.add("element 0");
list.add("element 1");
list.add("element 2");
list.remove(0); // 删除索引 0 处的元素
// 现在 list 包含 ["element 1", "element 2"]
删除后,后续元素索引自动前移。
清空 List(删除所有元素)
使用 clear() 方法:
List<String> list = new ArrayList<>();
list.add("object 1");
list.add("object 2");
list.clear(); // 清空列表
保留两个 List 的交集:retainAll()
retainAll(Collection<?> c) 会移除当前列表中不在参数集合中出现的所有元素,结果是两个集合的交集。
List<String> list = new ArrayList<>();
List<String> otherList = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
otherList.add("element 1");
otherList.add("element 3");
otherList.add("element 4");
list.retainAll(otherList);
// list 现在只包含 ["element 1", "element 3"]
获取 List 大小
使用 size() 方法:
int size = list.size();
获取子列表:subList()
subList(int fromIndex, int toIndex) 返回原列表的一个视图,包含从 fromIndex(含)到 toIndex(不含)的元素。
List<String> list = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 4");
List<String> sublist = list.subList(1, 3);
// sublist 包含 ["element 2", "element 3"]
注意:
toIndex对应的元素不包含在子列表中,类似于String.substring()。
将 List 转换为 Set
创建一个 Set 并将 List 中所有元素添加进去,自动去重:
List<String> list = new ArrayList<>();
list.add("element 1");
list.add("element 2");
list.add("element 3");
list.add("element 3"); // 重复
Set<String> set = new HashSet<>();
set.addAll(list);
// set 包含 {"element 1", "element 2", "element 3"}
将 List 转换为数组
转换为 Object[]
Object[] objects = list.toArray();
转换为指定类型数组
String[] strings = list.toArray(new String[0]);
即使传入长度为 0 的数组,返回的数组也会包含所有元素。
将数组转换为 List
使用 Arrays.asList():
String[] values = {"one", "two", "three"};
List<String> list = Arrays.asList(values);
注意:
Arrays.asList()返回的是固定大小的列表,不支持add()或remove()操作。如需可变列表,应包装为new ArrayList<>(Arrays.asList(...))。
对 List 进行排序
使用 Collections.sort() 方法。
排序实现了 Comparable 接口的对象
例如 String:
List<String> list = new ArrayList<>();
list.add("c");
list.add("b");
list.add("a");
Collections.sort(list); // 按自然顺序排序:["a", "b", "c"]
使用 Comparator 自定义排序
适用于未实现 Comparable 或需要自定义顺序的情况。
假设有一个 Car 类:
public class Car {
public String brand;
public String numberPlate;
public int noOfDoors;
public Car(String brand, String numberPlate, int noOfDoors) {
this.brand = brand;
this.numberPlate = numberPlate;
this.noOfDoors = noOfDoors;
}
}
按品牌排序:
List<Car> list = new ArrayList<>();
list.add(new Car("Volvo V40", "XYZ 201845", 5));
list.add(new Car("Citroen C1", "ABC 164521", 4));
list.add(new Car("Dodge Ram", "KLM 845990", 2));
Comparator<Car> carBrandComparator = new Comparator<Car>() {
@Override
public int compare(Car car1, Car car2) {
return car1.brand.compareTo(car2.brand);
}
};
Collections.sort(list, carBrandComparator);
使用 Lambda 表达式简化
Comparator<Car> byBrand = (c1, c2) -> c1.brand.compareTo(c2.brand);
Comparator<Car> byPlate = (c1, c2) -> c1.numberPlate.compareTo(c2.numberPlate);
Comparator<Car> byDoors = (c1, c2) -> c1.noOfDoors - c2.noOfDoors;
Collections.sort(list, byBrand);
Collections.sort(list, byPlate);
Collections.sort(list, byDoors);
遍历 List
有四种常见方式:
1. 使用 Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
// 处理 element
}
2. 使用 for-each 循环(推荐)
for (String element : list) {
System.out.println(element);
}
3. 使用传统 for 循环(带索引)
for (int i = 0; i < list.size(); i++) {
String element = list.get(i);
// 处理 element
}
4. 使用 Java Stream API
list.stream().forEach(element -> {
System.out.println(element);
});
Stream API 还支持过滤、映射、聚合等高级操作。详见 Java Stream API 教程。
更多细节请查阅 JavaDoc
本文仅涵盖最常用的操作(增删改查、遍历、排序等)。如需了解全部功能,请参考官方 Java List 接口文档(JavaDoc)。