课程信息
课程名称: 一课掌握Kotlin 突破开发语言瓶颈
课程章节: 案例:Generator 与标准库的序列生成器
课程讲师: bennyhuo
课程内容
本节是案例讲解课程,案例是通过Kotlin实现一个Python Generator。
案例目标
Python Generator样例
Kotlin实现效果
fun main() {
val nums = generator { start: Int ->
for (i in 0..5) {
yield(start + i)
}
}
val seq = nums(10)
for (j in seq) {
println(j)
}
}
先来看看generator { start: Int -> ... }
generator的定义如下:
fun <T> generator(block: suspend GeneratorScope<T>.(T) -> Unit): (T) -> Generator<T> {
...
}
- generator是一个泛型函数
- 它的接收一个挂起函数作为参数,挂起函数的类型为
suspend GeneratorScope<Int>.(Int) -> Unit
- 它的返回值类型是Generator
generator { start: Int -> ... }
使用了Lambda表达式的写法,根据上面对generator定义的理解,可以拆分成如下内容去理解:
val job: suspend GeneratorScope<Int>.(Int) -> Unit = {start: Int ->
for (i in 0..5) {
yield(start + i)
}
}
fun main() {
val nums = generator(job)
val seq = nums(10)
for (j in seq) {
println(j)
}
}
首先,generator使用了Lambda表达式的写法,当Lambda作为函数的最后一个参数传入时,可以写到括号外面。当Lambda写到括号外面后,括号内没有其他参数时,括号可以省略。我们将generator还原成如此:
generator({ start: Int ->
for (i in 0..5) {
yield(start + i)
}
})
括号中的内容实际上是一个匿名函数,而函数在Kotlin中是一等公民,它有自己的类型,可以赋值,可以传递,并在合适的条件下调用。
这个匿名函数的类型是suspend GeneratorScope<Int>.(Int) -> Unit
,那么我们就去定义一个属性job,它的类型是suspend GeneratorScope<Int>.(Int) -> Unit
val job: suspend GeneratorScope<Int>.(Int) -> Unit = {start: Int ->
for (i in 0..5) {
yield(start + i)
}
}
调用generator的时候,把job传递进去val nums = generator(job)
generator的返回值类型是一个函数类型(T) -> Generator<T>
,该函数类型的返回结果是一个Generator类型。本案例中,generator直接return一个Lambda表达式,Lambda表达式最后一行就是函数表达式的返回值,所以该它返回了一个GeneratorImpl实例。
fun <T> generator(block: suspend GeneratorScope<T>.(T) -> Unit): (T) -> Generator<T> {
return { parameter: T ->
GeneratorImpl(block, parameter)
}
}
Generator解析
GeneratorImpl是Generator接口的一个实现类。
interface Generator<T> {
operator fun iterator(): Iterator<T>
}
从实现效果知道,我们需要Generator可以迭代,所以我们的Generator需要重写iterator这个方法,而iterator在Kotlin中是一个运算符,所以iterator前面加上operator关键字,表明这是一个运算符重载。
GeneratorImpl就是对Generator接口的实现,它实现了iterator方法,在iterator中返回了一个GeneratorIterator实例
class GeneratorImpl<T>(private val block: suspend GeneratorScope<T>.(T) -> Unit, private val parameter: T): Generator<T> {
override fun iterator(): Iterator<T> {
return GeneratorIterator(block, parameter)
}
}
GeneratorIterator肯定得实现Iterator接口
override fun hasNext(): Boolean {
...
}
override fun next(): T {
...
}
在初始化seq变量后,对他进行遍历时,按照迭代原理,一开始就会先调用hasNext,判断是否有下一个元素,有的话就调用next,没有的话就结束,后续不断循环hasNext -> next这个流程,直到迭代结束。
为了使自定义的GeneratorIterator能够正常工作,案例定义了一个State密封类
sealed class State {
class NotReady(val continuation: Continuation<Unit>): State()
class Ready<T>(val continuation: Continuation<Unit>, val nextValue: T): State()
object Done: State()
}
然后通过State来实现hasNext和next的迭代逻辑
在GeneratorIterator初始化的时候,把状态设置为State.NotReady,开始迭代的时候会先调用hasNext,这时状态时NotReady,我们启动协程。
sealed class State {
class NotReady(val continuation: Continuation<Unit>): State()
class Ready<T>(val continuation: Continuation<Unit>, val nextValue: T): State()
object Done: State()
}
然后通过State来实现hasNext和next的迭代逻辑
private fun resume() {
when(val currentState = state) {
is State.NotReady -> currentState.continuation.resume(Unit)
}
}
override fun hasNext(): Boolean {
resume()
return state != State.Done
}
协程启动后,原来的执行流程会暂停,转去执行协程的方法体
{start: Int ->
for (i in 0..5) {
yield(start + i)
}
}
yield也是一个挂起方法,在它内部把状态从NotReady改为Ready
override suspend fun yield(value: T) = suspendCoroutine<Unit> {
continuation ->
state = when(state) {
is State.NotReady -> State.Ready(continuation, value)
is State.Ready<*> -> throw IllegalStateException("Cannot yield a value while ready.")
State.Done -> throw IllegalStateException("Cannot yield a value while done.")
}
}
yield执行完后,恢复到主流程,继续执行hasNext,hasNext执行完后就会去调用next。
next中判断到状态为Ready,就会把状态修改为NotReady,并把结果返回
override fun next(): T {
return when(val currentState = state) {
is State.NotReady -> {
resume()
return next()
}
is State.Ready<*> -> {
state = State.NotReady(currentState.continuation)
(currentState as State.Ready<T>).nextValue
}
State.Done -> throw IndexOutOfBoundsException("No value left.")
}
}
至此,一次迭代业务完成。
进入下一次迭代,由于状态时NotReady,会再次启动协程,执行yield把状态改为Ready,然后在next中获取结果,再把状态改为NotReady。
另外,由于foreach在懒序列中不会立即执行,所以for (i in 0…5) {…}会在每次协程启动的时候才迭代一次。
当for (i in 0…5) {…}迭代完了,会调用resumeWith,把状态修改为Done
override fun resumeWith(result: Result<Any?>) {
state = State.Done
result.getOrThrow()
}
状态为Done,hasNext返回false,真个迭代流程就执行完了。
学习总结
通过对案例的琢磨,除了对新增知识协程有了更深刻的认识外,还对之前章节的知识进行了一遍复习,对Kotlin函数类型,密封类型,Lambda表达式加深了印象。
共同学习,写下你的评论
评论加载中...
作者其他优质文章