先思考一个问题:“不同集合内部结构差异巨大——数组、链表、哈希表、树各有千秋——但 Java 迭代器却能用统一的方式遍历它们,这究竟是如何实现的?”
迭代器作为 Java 集合框架中最基础但又极具设计美感的组成部分,承担着遍历各种集合的“桥梁”作用。本文将从定义、功能、原理到接口来源,再到为何能统一遍历不同结构的集合进行深度解析,帮助你真正掌握 Iterator 的底层机制。
在 Java 中,迭代器(Iterator) 是集合框架中的一个接口,提供了一种 统一访问集合中元素 的方式,而不暴露集合内部的数据结构。
public interface Iterator {
boolean hasNext(); // 是否还有元素
E next(); // 获取下一个元素
void remove(); // 移除当前元素(可选)
}
遍历元素:不关心集合的底层实现(数组、链表、哈希表等);
逐个访问:通过 hasNext()
判断是否还有元素,next()
获取当前元素;
安全删除:remove()
方法可以删除当前元素(需要谨慎使用);
List list = new ArrayList<>();
list.add("A");
list.add("B");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Java 的迭代器基于经典的行为型设计模式:迭代器模式。
定义:为容器对象提供一种方法,顺序访问其内部元素,而不暴露其内部表示。
来看 ArrayList
中 iterator()
方法的实现:
public Iterator iterator() {
return new Itr();
}
内部类 Itr
是 ArrayList
的迭代器实现,逻辑大致如下:
private class Itr implements Iterator {
int cursor = 0; // 下一个要返回的元素索引,初始从0开始
int lastRet = -1; // 上一个返回元素的索引,初始-1表示还未返回任何元素
public boolean hasNext() {
return cursor != size; // 判断是否还有元素(cursor未到集合末尾)
}
public E next() {
if (cursor >= size) throw new NoSuchElementException(); // 超出范围抛异常
return elementData[lastRet = cursor++]; // 返回当前元素,cursor后移,记录lastRet
}
public void remove() {
if (lastRet < 0) throw new IllegalStateException(); // next()未调用或重复调用remove抛异常
ArrayList.this.remove(lastRet); // 调用ArrayList的remove删除元素
cursor = lastRet; // 调整cursor指向删除元素位置,保证遍历正确
lastRet = -1; // 重置lastRet防止重复remove
}
}
ArrayList 有一个 modCount
变量记录集合修改次数;
迭代器在创建时记录 expectedModCount = modCount
;
如果遍历过程中结构被修改(比如 add/remove),就会抛出 ConcurrentModificationException
;
这是为了避免并发修改造成的不一致状态。
你可能会疑惑:为什么任何 List、Set 等集合都可以调用 iterator()
方法?
Iterable
接口!public interface Iterable {
Iterator iterator();
}
所有可以被增强 for
循环(for-each)遍历的类都实现了 Iterable
接口。包括:
public interface Collection extends Iterable {}
public interface List extends Collection {}
➡️ ArrayList
、LinkedList
、HashSet
等集合类都继承自 Collection
,因此都实现了 iterator()
方法。
这是迭代器设计的最大亮点之一。
ArrayList
底层是数组;
LinkedList
是双向链表;
HashSet
是基于 HashMap
;
TreeSet
是红黑树。
它们内部结构千差万别,但都可以用如下代码统一遍历:
for (String s : someCollection) {
System.out.println(s);
}
因为每个集合类都提供了自己“定制”的迭代器实现。Java 只是通过统一的接口 Iterator
提供“统一的使用方式”,但实际的遍历逻辑是由集合内部自己实现的。
每个集合类都提供了自己的 iterator()
实现;
返回各自内部定制的迭代器(内部类实现);
每个迭代器都封装了对自身结构的遍历逻辑;
由于都实现了统一的 Iterator
接口,Java 代码可以“面向接口编程”:
public void printAll(Iterator> iterator) {
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
➡️ 这正体现了多态+设计模式的强大威力 —— 你只需要调用 iterator()
,而不需要知道背后是数组还是红黑树。
增强 for 循环本质就是语法糖,底层就是迭代器:
for (String s : list) {
System.out.println(s);
}
// 等价于:
Iterator it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
问题 | 解答 |
---|---|
什么是迭代器? | 是用于遍历集合中元素的对象,封装了遍历逻辑 |
iterator() 从哪来? | 来自 Iterable 接口,所有集合都实现了它 |
迭代器如何实现的? | 每个集合有自己定制的内部类,实现 Iterator 接口 |
为何能统一遍历? | 迭代器模式 + 多态,让用户“只关注接口,不关心结构” |
如果你自己实现一个数据结构,只要实现 Iterable
接口,也能用 for-each 遍历;
如果要支持并发遍历,需要考虑 fail-safe 的方式,如 CopyOnWriteArrayList
、ConcurrentHashMap
的迭代器;
我们回到最初的疑问:
不同集合类底层结构不同,为何都能通过
iterator()
统一遍历?
所有集合类都实现了 Iterable
接口,这意味着它们都提供了 iterator()
方法。
iterator()
方法会返回一个实现了 Iterator
接口的对象,这是集合类自己根据结构实现的。
不同集合的 Iterator
实现类根据自身结构(如数组、链表、哈希表、树)来决定如何遍历。
你拿到的只是一个 Iterator
对象,统一使用 hasNext()
+ next()
,无需关心其内部结构。
这正是多态 + 设计模式(迭代器模式)+ 面向接口编程的完美体现。