美文网首页
Kotlin中的Iterator与护士小姐姐

Kotlin中的Iterator与护士小姐姐

作者: 曹建峰 | 来源:发表于2020-12-12 23:54 被阅读0次
    Screen Shot 2020-12-12 at 11.53.12 PM.png

    隐喻:护士小姐姐和医生

    分诊

    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()
    }
    
    

    需要注意的细节:

    1. Kotlin中内嵌类需要 inner修饰
    2. 重载成员需要用override修饰
    3. 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
    --- 这里是分诊台 ---
    赵丽影  ♀       正在排队
    关晓童  ♀       正在排队
     杨蜜   ♀       正在排队
     柳颜   ♀       正在排队
     马匀   ♂       正在排队
    张一明  ♂       正在排队
    马化疼  ♂       正在排队
    李彦红  ♂       正在排队
    

    相关文章

      网友评论

          本文标题:Kotlin中的Iterator与护士小姐姐

          本文链接:https://www.haomeiwen.com/subject/lvkygktx.html