
隱喻:護(hù)士小姐姐和醫(yī)生

Iterator如同一個(gè)護(hù)士小姐姐。
護(hù)士小姐姐在診室外組織病人們排隊(duì)。
病人們?cè)趺磁判颍?br>
掛號(hào)的順序、遞交病歷本的順序還是顏值,
護(hù)士小姐姐說了算。
Iterator的調(diào)用者如同醫(yī)生。
醫(yī)生坐在診室里面,專心致志的給她面前的病人看病。
看完之后只需要喊一聲:“下一個(gè)”。
接下來我們一起看看Iterator是怎么做的。
從一個(gè)優(yōu)雅的例子開始
Kotlin官方文檔中有個(gè)例子Iterators,簡(jiǎn)潔優(yōu)雅。
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的優(yōu)點(diǎn)。
它將數(shù)據(jù)結(jié)構(gòu)封裝在類的內(nèi)部,調(diào)用者無需關(guān)心數(shù)據(jù)是如何存儲(chǔ)的。
并且有迭代器的類可以使用for in循環(huán)訪問,這使得代碼非常接近自然語言,易讀而便于維護(hù)。
簡(jiǎn)約的文檔并不能使我感到滿足
官網(wǎng)中例子對(duì)應(yīng)的英文文檔相當(dāng)?shù)暮?jiǎn)約,即便如此,所謂的kotlin中文網(wǎng)也沒有對(duì)應(yīng)的譯文。我只好手動(dòng)翻譯:
迭代器
在你的類中,你能通過實(shí)現(xiàn)iterator操作符來定義你自己的迭代器。
...此處省略剛才舉過的例子代碼十幾行...
1. 在類中定義一個(gè)迭代器。它必須命名為iterator,并且被關(guān)鍵字operator修飾。
2. 返回的迭代器,需包括下列成員函數(shù)
· next(): Animal
· hasNext(): Boolean
3. 使用自定義迭代器循環(huán)遍歷動(dòng)物園中的動(dòng)物
迭代器可以用擴(kuò)展函數(shù)的形式聲明。
例子中的迭代器相當(dāng)簡(jiǎn)單,僅僅是把自己的list成員的iterator透?jìng)鞒鰜?。如果有特殊需求怎么辦呢?
經(jīng)過摸索發(fā)現(xiàn)我們需要定義自己的Iterator類。
大體思路:
如果當(dāng)前數(shù)據(jù)中沒有合適的iterator,就自己定義一個(gè)內(nèi)部類xxIterator,
這個(gè)內(nèi)部類,可以訪問到外部類的所有數(shù)據(jù),
這個(gè)內(nèi)部類,可以將遍歷外部數(shù)據(jù)相關(guān)的邏輯和遍歷狀態(tài)封裝起來。
這個(gè)內(nèi)部類,符合Iterator接口規(guī)范(實(shí)現(xiàn)next和hasNext函數(shù))
最后將這個(gè)內(nèi)部類通過外部類的iterator接口提供給外面用。
關(guān)鍵代碼如下:
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()
}
需要注意的細(xì)節(jié):
- Kotlin中內(nèi)嵌類需要 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♀"
}
}
// 醫(yī)院
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("--- 這里是分診臺(tái) ---")
val hospital = Hospital(
listOf(
FemalePatient("趙麗影"),
FemalePatient("關(guān)曉童"),
FemalePatient(" 楊蜜 "),
FemalePatient(" 柳顏 ")
),
listOf(
MalePatient(" 馬勻 "),
MalePatient("張一明"),
MalePatient("馬化疼"),
MalePatient("李彥紅")
)
)
for (Patient in hospital) {
println("${Patient.name()}\t正在排隊(duì)")
}
}
代碼說明:
首先我們聲明一個(gè)基類“Patient”有共同的屬性“name”。
然后按照性別派生了MalePatient和FemalePatient,name()方法中提供了個(gè)性化的返回。
“Hospital”類中包含兩個(gè)隊(duì)列,malePatients和femalePatients
內(nèi)嵌類“PatientIterator”中封裝了對(duì)兩個(gè)病人列表的遍歷邏輯:
先遍歷女病人列表,然后遍歷男病人列表。
如果都遍歷完則通過hasNext返回沒有病人了
“Hospital”中iterator()函數(shù)創(chuàng)建并返回了PatientIterator的實(shí)例
最后的main函數(shù)中我們使用forIn語句循環(huán)hospital實(shí)例得到隊(duì)列中的每個(gè)病人。
運(yùn)行結(jié)果:
$ ~/.sdkman/candidates/kotlin/current/bin/kotlinc hospital.kt -include-runtime -d hospital.jar && java -jar hospital.jar
--- 這里是分診臺(tái) ---
趙麗影 ♀ 正在排隊(duì)
關(guān)曉童 ♀ 正在排隊(duì)
楊蜜 ♀ 正在排隊(duì)
柳顏 ♀ 正在排隊(duì)
馬勻 ♂ 正在排隊(duì)
張一明 ♂ 正在排隊(duì)
馬化疼 ♂ 正在排隊(duì)
李彥紅 ♂ 正在排隊(duì)