Go无内置Iterator接口,推荐用闭包func() (T, bool)实现单次安全遍历,或chan T支持并发/中断;避免自定义Iterator结构体,因其易误用、难测试且违背Go哲学。

Go 里没有内置 Iterator 接口,得自己定义
Go 语言标准库不提供 Iterator 或 Iterable 接口,不像 Java/C# 那样有统一契约。这意味着你没法直接写 for item := range collection 除非集合本身实现了 range 支持(即有 Len() + Index(int) T 或是切片/映射/通道)。自定义集合要支持类似遍历,必须手动暴露一种“可逐个取值”的能力。
常见错误是试图模仿其他语言的 hasNext() / next() 两步法——这在 Go 里既啰嗦又容易出错(比如并发下状态不一致、漏调 hasNext 导致 panic)。
- 推荐做法:返回一个函数闭包,每次调用返回下一个元素和是否结束,例如
func() (T, bool) - 更 Go 的方式:返回一个
chan T,用for v := range ch消费;但要注意协程泄漏和关闭时机 - 如果集合支持随机访问且数据量不大,直接实现
Len()和At(int) T,让使用者用传统 for 循环,反而最清晰
用 func() (T, bool) 实现安全的单次遍历
这是最轻量、无 goroutine、无内存泄漏风险的做法,适合大多数不可变或只读集合。闭包内部维护当前索引或游标,调用时推进并返回值。
示例:一个简单整数列表的迭代器
立即学习“go语言免费学习笔记(深入)”;
type IntList struct {
data []int
}
<p>func (l *IntList) Iterator() func() (int, bool) {
i := 0
return func() (int, bool) {
if i >= len(l.data) {
return 0, false
}
v := l.data[i]
i++
return v, true
}
}- 闭包捕获的是
i变量本身,不是快照,所以每次调用都推进状态 - 返回的函数只能被消费一次;重复调用会继续往后走,不会重置 —— 这是设计使然,避免隐式重置带来的歧义
- 不要在迭代过程中修改底层
data,否则行为未定义;如需支持边遍历边修改,得加锁或改用通道方案
用 chan T 实现可中断、可并发的遍历
当需要支持外部中断(比如 ctx.Done())、或遍历过程涉及 IO/网络/计算耗时操作时,通道方案更合适。但它引入了 goroutine 和 channel 生命周期管理的复杂度。
典型错误:忘了关闭 channel,导致 for range 永远卡住;或在遍历中途 panic 未 recover,goroutine 泄漏。
- 务必用
defer close(ch)确保通道关闭,哪怕提前 return - 把
ctx传入迭代方法,在循环中 select 判断ctx.Done() - 别在通道发送端做重试或阻塞操作,否则消费者可能永久等待
- 如果集合很小,通道方案反而增加开销;纯内存遍历优先选闭包方案
为什么不要实现 Iterator 结构体带 Next() 方法
有人会封装一个 type Iterator struct { ... },带 Next() (T, bool) 和 HasNext() bool。这在 Go 中属于反模式。
问题在于:状态分散、易误用、难测试。
- 用户可能调用
HasNext()后忘记调Next(),或连调两次Next()跳过元素 - 无法自然融入
for range,还得写 while 循环,失去 Go 的简洁性 - 多个 goroutine 同时调用同一个
Iterator实例会竞争状态,必须加锁,而锁又破坏了无共享的 Go 哲学 - 测试时得反复构造、重置实例;闭包或 channel 方案天然无状态或单次语义,更容易验证
真正需要多次遍历?那就多次调用 Iterator() 方法,每次得到全新闭包或新 channel —— 显式、无副作用、符合直觉。










