List = 家族里的点名册——存取顺序一致、每个位置有下标、允许重复。四个下标专属方法 + 普通 for 遍历都是它独有的;ArrayList 和 LinkedList 是同一合同的两个干法。
7.3 List接口
1. List:带下标的子合同
7.2 的家族地图里,List 是 Collection 底下的子合同——总合同的规矩它全守(add/remove/contains 都会),还额外加了一条:
必须有序、必须能按下标找人、允许重复。
Collection(总合同)
↑ extends
List(子合同:带下标的点名册)
↑ implements
ArrayList(数组干法) LinkedList(链表干法)
「有序」的准确含义:存进去什么顺序,取出来还是什么顺序——不是按大小排序,是"排队"。
| 特点 | 含义 | 对照 |
|---|---|---|
| 有序 | 存取顺序一致 | Set 就不保证(以后见) |
| 带下标 | 每个位置有编号,从 0 起 | 和数组(2.7)一个规矩 |
| 可重复 | 同一元素能存多份 | Set 到此为止 |
2. 四个下标专属方法(List 特有)
总合同里没有、只有 List 才有的操作:
List<String> list = new ArrayList<>();
list.add("波仔");
list.add("墨池");
list.add("AI");
list.add(1, "Java"); // 插队:下标 1 处插入,后面全体后挪
System.out.println(list.get(2)); // 墨池:按下标点名
list.set(0, "BoZai"); // 换人:下标 0 的内容替换
list.remove(2); // 撤人:删下标 2 的元素
System.out.println(list.indexOf("AI")); // 找人:返回首次出现的下标,没有返回 -1
| 方法 | 作用 | 备注 |
|---|---|---|
add(下标, e) |
指定位置插入 | 原位置的元素及后面全体后挪 |
get(下标) |
按下标取 | 越界抛 IndexOutOfBoundsException(老朋友) |
set(下标, e) |
按下标改 | 是"替换",不是"插入" |
remove(下标) |
按下标删 | 后面的全体前挪 |
indexOf(e) |
按内容查下标 | 找不到返回 -1 |
remove(1) 和 remove("波仔") 是重载(2.6 方法):参数是 int → 按下标删;参数是对象 → 按内容删(走 equals,7.2 的规矩)。看参数类型就能判断删的是什么。小心 List<Integer> 里的坑:remove(1) 删的是下标 1,不是值 1——想按值删要写 remove(Integer.valueOf(1))。
3. 三种遍历方式
List 有了下标,就多了一种 Collection 没有的遍历:
List<String> list = new ArrayList<>();
list.add("波仔"); list.add("墨池"); list.add("AI");
// 姿势一:普通 for —— List 专属(因为 get + size 都是下标玩法)
for (int i = 0; i < list.size(); i++) {
System.out.println(i + " : " + list.get(i));
}
// 姿势二:增强 for —— 全家通用,只读场景首选
for (String s : list) {
System.out.println(s);
}
// 姿势三:迭代器 —— 遍历中要删元素时的正解
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("AI")) {
it.remove(); // 用迭代器的 remove,不用集合的
}
}
只读 → 增强 for;要下标或边遍历边改位置 → 普通 for;要边遍历边删 → 迭代器(it.remove())。
4. ArrayList 与 LinkedList:同一合同,两个干法
List 只是合同,干活的是实现类:
| ArrayList | LinkedList | |
|---|---|---|
| 底层 | 数组(2.7 那种连续格子) | 双向链表(钩子串珠子) |
| 查找 get(i) | 快——下标直达 | 慢——从两头挨个走 |
| 中间增删 | 慢——后面全体挪动 | 快——摘钩子再挂钩子 |
| 头尾增删 | 尾部快,头部慢 | 两头都快 |
选型口诀:查得多、随机访问多 → ArrayList;头尾频繁增删 → LinkedList。新手默认无脑 ArrayList——绝大多数场景它就是最优解,LinkedList 认识即可。
List<String> list = new ArrayList<>();——左边写 List 合同,右边换实现只改一个词。7.2 的面向接口姿势在 List 这层同样成立。
5. 并发修改异常:7.2 的钩子正式收网
7.2 埋的那句"遍历中不能增删",现在讲清楚:
List<String> list = new ArrayList<>();
list.add("A"); list.add("B"); list.add("C");
for (String s : list) { // 增强 for
if (s.equals("B")) {
list.remove(s); // 炸!ConcurrentModificationException
}
}
为什么炸:增强 for 底层是迭代器,迭代器自己记了一本"预期次数"的账;你绕过它直接改集合,账对不上,下次 next() 当场翻脸——这是运行时异常(5.2 收容所那派),编译器不拦。
正解(二选一):
// 解法一:迭代器 + it.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("B")) {
it.remove(); // 迭代器自己删,账目一致,不炸
}
}
// 解法二:普通 for 倒着删(List 专属福利)
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).equals("B")) {
list.remove(i); // 倒着走,删掉后面的不影响前面的下标
}
}
倒着删为什么行:正着删会把后面的元素往前顶、下标错位漏人;倒着删动的全是"已经走过"的位置,永远不会错位。
6. 动手试一试(30 秒实验)
新建 Demo03(import java.util.*;):
实验 1:add 三个元素后 list.add(1, "X"),打印——看"X 插队、后面后挪";再用 list.set(1, "Y") 对比——看 set 是替换不是插入。
实验 2:List<Integer> nums = new ArrayList<>(); 加 10、20、30,调 nums.remove(1) 再打印——看到删的是下标 1 的 20,不是值 1(想删值 1 得 remove(Integer.valueOf(1)))。
实验 3:增强 for 里 list.remove(s)——亲眼看 ConcurrentModificationException 爆出(和 7.2 实验 3 对照)。
实验 4:同一个删除需求分别用迭代器 it.remove() 和普通 for 倒着删——两种正解都跑通,再体会正着删为什么会漏人(add 两个连续目标试试)。
7. 常见误区
看参数类型:int → 按下标;对象 → 按内容。List<Integer> 里这个歧义是真会咬人的,删值要 remove(Integer.valueOf(1))。
set(下标, e) 是替换该位置,元素个数不变;插入要用 add(下标, e)。
下标错位:删掉 i 后,原 i+1 顶到 i,i++ 直接跳过了它——漏人。要删就倒着循环或用迭代器。
有序 = 存取顺序一致(排队),不是 sorted(按值排列)。想按值排序是以后 Arrays.sort / 集合排序的事。
不成立。头尾增删 LinkedList 更快;中间插入它还得先"走到"那个位置(查找慢),不一定占便宜。别背"谁快谁慢"的口诀,记住底层是数组还是链表,自己推。
8. 自测一下
有序(存取顺序一致)、带下标(按下标操作)、可重复。三个都是 Collection 合同里没有的。
前者按下标(参数 int),后者按内容(参数对象,走 equals)。依据是重载(2.6 方法)按参数类型区分。
remove(1) 匹配的是 int 参数版 = 按下标删。要删值 1 必须 remove(Integer.valueOf(1))——让参数变成 Integer 对象,走按内容版。
增强 for 底层是迭代器,迭代器记着预期修改次数;绕过它改集合账目对不上,next() 时翻脸(运行时异常)。正解:① 迭代器 it.remove();② 普通 for 倒着删。
ArrayList 底层是数组,LinkedList 底层是双向链表。头部插入选 LinkedList:链表挂钩子即可;ArrayList 头部插入要把全部元素整体后挪,元素越多越慢。(但注意:如果插入前还要先查找,LinkedList 找位置也慢,实战仍多为 ArrayList。)
9. 下一步学什么
- ☑ 7.4 集合遍历 ✅ —— 已完成:五种遍历姿势全景、迭代器指针原理、ListIterator 倒走、遍历中删除四写法总账、forEach+Lambda 正式上岗。点此复习
相关笔记
- 第七章 集合/7.2 Collection接口 ← 上一篇:总合同与 equals 规矩,本节细读 List 子合同
- 第七章 集合/7.1 集合概述:remove 歧义在 7.1 埋钩,本节分清
- 第二章 Java编程基础/2.7 数组:ArrayList 底层就是它的"自动挡"
- 第五章 异常/5.2 运行时异常与编译时异常:ConcurrentModificationException 是运行时异常那派
- 第二章 Java编程基础/2.6 方法:remove(int) 与 remove(Object) 的区分依据——重载