隐喻:护士小姐姐和医生
Iterator如同一个护士小姐姐。
护士小姐姐在诊室外组织病人们排队。
病人们怎么排序?
挂号的顺序、递交病历本的顺序还是颜值,
护士小姐姐说了算。
Iterator的调用者如同医生。
医生坐在诊室里面,专心致志的给她面前的病人看病。
看完之后只需要喊一声:“下一个”。
接下来我们一起看看Iterator是怎么做的。
从一个优雅的例子开始
Kotlin官方文档中有个例子Iterators,简洁优雅。
class Animal(val name: String)
class Zoo(val animals: List<Animal>) {
operator fun iterator(): Iterator<Animal> { // 1
return animals.iterator() // 2
}
}
fun main() {
val zoo = Zoo(listOf(Animal("zebra"), Animal("lion")))
for (animal in zoo) { // 3
println("Watch out, it's a ${animal.name}")
}
}
透过它我们来看看Iterator的优点。
它将数据结构封装在类的内部,调用者无需关心数据是如何存储的。
并且有迭代器的类可以使用for in循环访问,这使得代码非常接近自然语言,易读而便于维护。
简约的文档并不能使我感到满足
官网中例子对应的英文文档相当的简约,即便如此,所谓的kotlin中文网也没有对应的译文。我只好手动翻译:
迭代器
在你的类中,你能通过实现iterator操作符来定义你自己的迭代器。
...此处省略刚才举过的例子代码十几行...
1. 在类中定义一个迭代器。它必须命名为iterator,并且被关键字operator修饰。
2. 返回的迭代器,需包括下列成员函数
· next(): Animal
· hasNext(): Boolean
3. 使用自定义迭代器循环遍历动物园中的动物
迭代器可以用扩展函数的形式声明。
例子中的迭代器相当简单,仅仅是把自己的list成员的iterator透传出来。如果有特殊需求怎么办呢?
经过摸索发现我们需要定义自己的Iterator类。
大体思路:
如果当前数据中没有合适的iterator,就自己定义一个内部类xxIterator,
这个内部类,可以访问到外部类的所有数据,
这个内部类,可以将遍历外部数据相关的逻辑和遍历状态封装起来。
这个内部类,符合Iterator接口规范(实现next和hasNext函数)
最后将这个内部类通过外部类的iterator接口提供给外面用。
关键代码如下:
class Hospital(val femalePatients: List<FemalePatient>, val malePatients: List<MalePatient>) {
inner class PatientIterator : Iterator<Patient> {
//...
public override operator fun next(): Patient {
//...
}
public override operator fun hasNext(): Boolean {
//...
}
}
operator fun iterator(): Iterator<Patient> = PatientIterator()
}
需要注意的细节:
- Kotlin中内嵌类需要 inner修饰
- 重载成员需要用override修饰
- next、hasNext和iterator 需要用operator修饰
完整代码:
package top.ovo.hospital
// 基类病人
open class Patient(val name: String) {
open fun name(): String {
return name
}
}
// 男病人
class MalePatient(name: String) : Patient(name) {
override fun name(): String {
return "$name\t♂"
}
}
// 女病人
class FemalePatient(name: String) : Patient(name) {
override fun name(): String {
return "$name\t♀"
}
}
// 医院
class Hospital(val femalePatients: List<FemalePatient>, val malePatients: List<MalePatient>) {
inner class PatientIterator : Iterator<Patient> {
private var femalePatientsIndex: Int = 0
private var malePatientsIndex: Int = 0
public override operator fun next(): Patient {
if (femalePatientsIndex <femalePatients.size) {
return femalePatients.get(femalePatientsIndex++)
}
return malePatients.get(malePatientsIndex++)
}
public override operator fun hasNext(): Boolean {
return femalePatientsIndex < femalePatients.size ||
malePatientsIndex < malePatients.size
}
}
operator fun iterator(): Iterator<Patient> = PatientIterator()
}
fun main() {
println("--- 这里是分诊台 ---")
val hospital = Hospital(
listOf(
FemalePatient("赵丽影"),
FemalePatient("关晓童"),
FemalePatient(" 杨蜜 "),
FemalePatient(" 柳颜 ")
),
listOf(
MalePatient(" 马匀 "),
MalePatient("张一明"),
MalePatient("马化疼"),
MalePatient("李彦红")
)
)
for (Patient in hospital) {
println("${Patient.name()}\t正在排队")
}
}
代码说明:
首先我们声明一个基类“Patient”有共同的属性“name”。
然后按照性别派生了MalePatient和FemalePatient,name()方法中提供了个性化的返回。
“Hospital”类中包含两个队列,malePatients和femalePatients
内嵌类“PatientIterator”中封装了对两个病人列表的遍历逻辑:
先遍历女病人列表,然后遍历男病人列表。
如果都遍历完则通过hasNext返回没有病人了
“Hospital”中iterator()函数创建并返回了PatientIterator的实例
最后的main函数中我们使用forIn语句循环hospital实例得到队列中的每个病人。
运行结果:
$ ~/.sdkman/candidates/kotlin/current/bin/kotlinc hospital.kt -include-runtime -d hospital.jar && java -jar hospital.jar
--- 这里是分诊台 ---
赵丽影 ♀ 正在排队
关晓童 ♀ 正在排队
杨蜜 ♀ 正在排队
柳颜 ♀ 正在排队
马匀 ♂ 正在排队
张一明 ♂ 正在排队
马化疼 ♂ 正在排队
李彦红 ♂ 正在排队