1. Iterable 接口
在 Java 中,几乎所有集合(除 Map 外)都实现了 Iterable 接口。这意味着它们可以被顺序遍历——一个接一个,而无需关心内部结构的细节。对程序员来说,这就像“集合自带一种遍历所有元素的方式”。
Iterable 接口只定义了一个方法:
Iterator<E> iterator();
iterator() 方法返回一个 Iterator 对象——一个“助手”,它知道如何一步步遍历集合。得益于它,常见的 for-each 循环才能工作:
for (ElementType e : collection) {
// ...
}
——在幕后正是这个 Iterator 在工作。额外的好处是:借助它可以在遍历时通过 remove() 安全删除元素。如果用普通遍历直接删除,很容易触发 ConcurrentModificationException。
2. Iterator 接口
Iterator 就像一个“快递员”,能够按照集合的遍历顺序逐个访问你的元素而不遗漏。
| 方法 | 说明 |
|---|---|
|
是否还有下一个可遍历的元素? |
|
返回下一个元素并移动到该位置 |
|
安全删除当前元素,不会出错 |
通过 Iterator 遍历集合的示例
import java.util.*;
public class IteratorDemo {
public static void main(String[] args) {
List<String> tasks = new ArrayList<>();
tasks.add("给猫咪顺毛");
tasks.add("做作业");
tasks.add("看剧");
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
System.out.println("任务: " + task);
}
}
}
这里发生了什么?
- 通过 tasks.iterator() 获取迭代器。
- 当存在下一个元素(hasNext() 返回 true)时,通过 next() 取出并输出。
- 迭代器会自行维护遍历顺序——你无需知道集合内部如何存储元素。
3. 有了循环为什么还需要 Iterator?
使用 Iterator 可以遍历任何集合,即使它没有下标(例如 Set)。这是一种与具体集合类型无关的通用方式。
安全删除元素
常见需求:遍历集合并删除某些元素。如果用 for-each 来做,可能会遇到错误:
for (String task : tasks) {
if (task.contains("猫")) {
tasks.remove(task); // 砰!ConcurrentModificationException
}
}
为什么会这样? 在由迭代器发起的遍历过程中,集合并不期望其结构被直接修改。
正确做法:
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("猫")) {
it.remove(); // 一切顺利!
}
}
为什么不能直接使用下标?
因为并非所有集合都有下标。例如,HashSet 或 TreeSet 并不存在“第 5 个元素”的概念。Iterator 在任何集合上都可用——这正是它的强大之处。
4. for-each 的细节
增强 for 循环(for-each)早在 Java 5 就出现了。本质上它是语法糖,使遍历元素变得尽可能简单:
for (String task : tasks) {
System.out.println("任务: " + task);
}
在底层,编译器会调用 iterator(),通过 hasNext() 检查并用 next() 取出元素。读起来几乎就像一句自然语言:“对于列表中的每个任务”。
什么时候不适合用 for-each?
- 需要在遍历过程中删除元素(for-each 不能直接调用 remove())。
- 需要访问下标,例如按位置替换元素。
- 你在使用 Map——它是“键-值”对,遍历需要专门的逻辑。
5. 遍历 Map:技巧与注意事项
Map 接口并未直接实现 Iterable,因为它是“键-值”对的集合。尽管如此,Map 提供了便捷的视图用于遍历。
按键遍历
Map<String, String> users = new HashMap<>();
users.put("bob", "bob@example.com");
users.put("alice", "alice@gmail.com");
for (String login : users.keySet()) {
System.out.println("登录名: " + login);
}
按值遍历
for (String email : users.values()) {
System.out.println("Email: " + email);
}
按键值对遍历
最通用的方式是遍历 entrySet():
for (Map.Entry<String, String> entry : users.entrySet()) {
System.out.println("登录名: " + entry.getKey() + ", Email: " + entry.getValue());
}
有趣的事实: Entry 是 Map 的内部接口,拥有 getKey() 和 getValue() 方法。这样你可以同时获得键和值。
通过 Iterator 遍历
Iterator<Map.Entry<String, String>> it = users.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
// 甚至可以安全地删除元素:
if (entry.getKey().startsWith("v")) {
it.remove();
}
}
6. 实战示例:遍历集合如何帮助你的应用
示例:输出用户的全部任务
List<String> tasks = new ArrayList<>();
tasks.add("做作业");
tasks.add("给猫咪顺毛");
tasks.add("看剧");
System.out.println("你今天的任务:");
for (String task : tasks) {
System.out.println("- " + task);
}
现在删除所有包含 "猫" 的任务:
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("猫")) {
it.remove();
}
}
System.out.println("剩余任务:");
for (String task : tasks) {
System.out.println("- " + task);
}
示例:通过 Set 遍历唯一登录名
Set<String> logins = new HashSet<>();
logins.add("bob");
logins.add("alice");
logins.add("masha");
for (String login : logins) {
System.out.println("用户: " + login);
}
注意:Set 的输出顺序可能是任意的!
示例:遍历 Map 以显示用户
Map<String, String> users = new HashMap<>();
users.put("bob", "bob@example.com");
users.put("alice", "alice@gmail.com");
for (Map.Entry<String, String> entry : users.entrySet()) {
System.out.println("登录名: " + entry.getKey() + ", Email: " + entry.getValue());
}
7. Iterator.remove(): 安全地删除元素
初学者最常见的错误之一,是试图在 for-each 遍历时删除元素。迭代器通过 remove() 解决了这一问题。
工作原理:
- 调用 it.remove() 会删除当前元素——即上一次 next() 返回的那个。
- 这是安全的:集合不会抛出 ConcurrentModificationException。
示例:
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = numbers.iterator();
while (it.hasNext()) {
int n = it.next();
if (n % 2 == 0) {
it.remove(); // 删除所有偶数
}
}
System.out.println(numbers); // [1, 3, 5]
集合遍历示意图
+---------+ +---------+ +---------+
| Element | --> | Element | --> | Element | ...
+---------+ +---------+ +---------+
^ ^
| |
next() next()
迭代器会按元素逐步前进,直到 hasNext() 返回 false。
9. 使用 Iterator 和遍历集合时的常见错误
错误 №1:在 for-each 遍历期间修改集合。
尝试在 for-each 内直接删除元素会导致 ConcurrentModificationException:
for (String task : tasks) {
if (task.contains("猫")) {
tasks.remove(task); // 砰!ConcurrentModificationException
}
}
请使用 Iterator 及其 remove()。
错误 №2:在 next() 之前调用 remove()。
必须先通过 next() 获取当前元素,否则迭代器不知道要删除哪个。
Iterator<String> it = tasks.iterator();
it.remove(); // 错误!必须先调用 next()
错误 №3:试图直接在 for-each 中遍历 Map。
Map 并未直接实现 Iterable——请使用 keySet()、values() 或 entrySet()。
Map<String, String> users = new HashMap<>();
// for (String entry : users) { ... } // 错误:不能这样
for (Map.Entry<String, String> e : users.entrySet()) {
// 正确写法
}
错误 №4:用迭代器遍历时在迭代器之外修改集合。
遍历过程中只能通过 it.remove() 删除元素,不能通过集合的方法直接删除。
Iterator<String> it = tasks.iterator();
while (it.hasNext()) {
String task = it.next();
if (task.contains("猫")) {
tasks.remove(task); // 错误!应该使用 it.remove()
}
}
GO TO FULL VERSION