1. 类 ArrayList
在 Java 中,接口 List 是一种契约:“我是一个有序的元素集合,你可以通过编号(索引)获取元素”。
列表(List)的关键特性:
- 元素按确定的顺序存储(不同于 Set)。
- 可以存储重复元素(例如:两次出现 "Vasya")。
- 可以按索引访问元素:第一个元素索引为 0,第二个为 1,以此类推。
- 可以在列表的任何位置添加、删除元素。
接口 List 最常见的实现是 ArrayList。它在内部使用普通数组,随着数据增多自动扩容。
如何创建 ArrayList?
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(String[] args) {
// 创建字符串列表
List<String> students = new ArrayList<>();
}
}
说明
- List<String> 是“字符串列表”类型的变量。通过接口来声明变量,通过具体类(ArrayList)来创建对象。
- 尖括号 <String> 指定列表元素的类型(泛型,generics)。
ArrayList(以及任何 List)的常用方法:
- add(element) —— 在列表末尾添加元素。
- add(index, element) —— 在指定索引处插入元素。
- get(index) —— 按索引获取元素。
- set(index, element) —— 按索引替换元素。
- remove(index) —— 按索引删除元素。
- remove(Object) —— 删除与传入对象相等的第一个元素。
- size() —— 获取列表中元素的数量。
示例:操作学生列表
import java.util.ArrayList;
import java.util.List;
public class StudentListDemo {
public static void main(String[] args) {
List<String> students = new ArrayList<>();
// 添加学生
students.add("Vasya");
students.add("Petya");
students.add("Masha");
// 输出所有学生
System.out.println("学生列表: " + students);
// 获取第一个学生
String first = students.get(0);
System.out.println("第一个学生: " + first);
// 修改第二个学生的名字
students.set(1, "Pavel");
System.out.println("修改后: " + students);
// 删除Masha
students.remove("Masha");
System.out.println("删除Masha后: " + students);
// 列表大小
System.out.println("学生总数: " + students.size());
}
}
结果:
学生列表: [Vasya, Petya, Masha]
第一个学生: Vasya
修改后: [Vasya, Pavel, Masha]
删除Masha后: [Vasya, Pavel]
学生总数: 2
这与我们的应用有什么关系?
假设我们有一个学生任务管理应用。现在可以用 List<String> 来存储任务列表,而不是用数组,并且可以动态添加新任务。
2. 类 LinkedList:当插入和删除的速度更重要
LinkedList 是列表的另一种实现。它是由双向节点串成的链,每个节点都知道前驱和后继。就像一列火车:在中间插入“车厢”很快,不需要重排整个车列。
创建 LinkedList
import java.util.LinkedList;
import java.util.List;
public class Example {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
}
}
LinkedList 的特点
- 在列表开头和中间进行插入、删除速度快。
- 按索引访问较慢(要找到第 100 个元素,需要沿链逐个前进)。
- 适合不仅在末尾,也经常在开头或中间添加/删除元素的场景。
示例:使用 LinkedList
import java.util.LinkedList;
import java.util.List;
public class TaskListDemo {
public static void main(String[] args) {
List<String> tasks = new LinkedList<>();
tasks.add("起床");
tasks.add("吃早餐");
tasks.add("去上课");
// 在开头添加任务
tasks.add(0, "设定闹钟");
System.out.println("任务列表: " + tasks);
// 删除第一项任务(最早的一项)
tasks.remove(0);
System.out.println("删除第一项任务后: " + tasks);
}
}
3. 对比 ArrayList 与 LinkedList
| 指标 | |
|
|---|---|---|
| 底层结构 | 数组 | 双向链表 |
| 按索引的快速访问 | 是 (O(1)) | 否 (O(n)) |
| 在开头/中间快速插入/删除 | 否 (O(n)) | 是 (O(1) — 如果有引用) |
| 在末尾快速插入/删除 | 是 (通常为 O(1)) | 是 (O(1)) |
| 内存 | 占用更少内存 | 占用更多(需要额外存储前后指针) |
| 典型场景 | 频繁按索引访问 | 频繁插入/删除 |
简单规则:
- 需要按编号快速访问 —— 使用 ArrayList。
- 经常在开头或中间添加/删除 —— 使用 LinkedList。
4. 列表的常见操作
遍历列表元素
普通 for 循环
for (int i = 0; i < students.size(); i++) {
System.out.println("学生 #" + i + ": " + students.get(i));
}
for-each 循环(最常用的方式)
for (String name : students) {
System.out.println("姓名: " + name);
}
Lambda 表达式(Java 8+)
students.forEach(name -> System.out.println("姓名: " + name));
查找元素
- contains(element) —— 如果列表中存在该元素则返回 true。
- indexOf(element) —— 返回元素第一次出现的索引;若未找到则返回 -1。
if (students.contains("Vasya")) {
System.out.println("Vasya在列表中!");
}
int index = students.indexOf("Vasya");
System.out.println("Vasya的索引: " + index);
清空列表
clear() —— 删除所有元素。
students.clear();
System.out.println("清空后的列表: " + students);
5. 何时使用 ArrayList,何时使用 LinkedList?
可以这样思考:ArrayList 适用于需要按索引快速访问元素,且列表不经常变更的场景。例如很长的用户列表或消息历史——以读取为主。
相反,LinkedList 在你经常在开头/中间插入或删除元素时更有用。典型如队列、栈或操作撤销历史。
在实践中最常用的还是 ArrayList。而 LinkedList 更像是“备用工具”:平时放在工具箱里,但有时恰好能派上用场。
6. 使用列表时的常见错误
错误 1:索引越界。 最常见的情况是访问不存在的索引。如果列表只有三个元素,却写了 students.get(5),会抛出 IndexOutOfBoundsException。访问前请检查 size()。
错误 2:遍历时删除元素。 在使用 for-each 遍历并同时删除元素时,会触发 ConcurrentModificationException。对于复杂的删除操作,请使用按索引的循环或 Iterator。
错误 3:错误的对象比较。 如果你存储的是自定义对象(例如 Student),contains 和 remove 依赖 equals。若未重写它,比较将基于引用而非内容。
错误 4:使用原生类型(raw types)。 不要写 List list = new ArrayList() —— 请始终指定元素类型:List<String> list = new ArrayList<>()。泛型能避免错误并让代码更清晰。
GO TO FULL VERSION