前言
Kotlin 语法糖的总结和原理分析。
Kotlin 有许多有用的语法糖,比方扩展函数、object 单例、apply/run/with 等内置函数,对于开发者来说非常的友爱的方便。简略整理和总结包括但不限于上述这些语法糖的内容。
Syntactic Sugar
内置函数
kotlin-stdlib 内的 Standard.kt 文件内界说了几个比较有用的顶层函数
比方 apply/with/run/let/also
等,这几个函数的功用比较相似,但又稍微有些差异,在此整理一下。
- 示例
fun main() {
val sugar = Sugar("mike", 21, true)
printInfo(sugar)
val letResult = sugar.let {
it.name = "let"
it.age = 9
}
printInfo(letResult)
val alsoResult = sugar.also {
it.name = "also"
it.age = 13
}
printInfo(alsoResult)
val withResult = with(sugar) {
name = "with"
age = 10
}
printInfo(withResult)
val runResult = sugar.run {
name = "run"
age = 11
}
printInfo(runResult)
val applyResult = sugar.apply {
name = "apply"
age = 12
}
printInfo(applyResult)
}
output
Sugar(name=mike, age=21, happy=true) : com.ext.Sugar
kotlin.Unit : kotlin.Unit // let
Sugar(name=also, age=13, happy=true) : com.ext.Sugar // also
kotlin.Unit : kotlin.Unit // with
kotlin.Unit : kotlin.Unit // run
Sugar(name=apply, age=12, happy=true) : com.ext.Sugar // apply
首先从回来成果,能够看到,默许情况下 apply 和 also 回来的都是当时对象,let/with/run 回来的是 kotlin.Unit ,也便是在 Lamdba 表达式中如果没有显现的在最终一行写回来值,那么 kotlin.Unit 便是回来值,能够理解为 Java 中的 Void。
- 参数
其次从 lambda 表达式的参数能够看出,it 和 also 都是 it ,剩下的 run/with/apply 都是 this 。其实 run 和 with 是的表现是完全一致的,只是调用方式不同罢了,run 只需要一个参数,而 with 需要把接受者和 lambda 一起传入。
类型 | 参数 | 回来值 |
---|---|---|
let | it | lambda 表达式最终一行,默许为 kotlin.Unit |
also | it | 接受者,即调用办法的对象 |
apply | this | 接受者,即调用办法的对象 |
with | this | lambda 表达式最终一行,默许为 kotlin.Unit |
run | this | lambda 表达式最终一行,默许为 kotlin.Unit |
原理剖析
总的来说,这几个内置函数的完成是高度相似的,都是使用了 Kotlin 高阶函数的特性。可是他又是怎么完成这些微妙的差异的那?我们能够比照一下 let
和 also
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
- 能够看到
block: (T) -> R
block 函数的参数类型便是 T,也便是调用者。因而 lambda 表达式的参数名称便是it
- 再看回来值
let
直接回来了 block 函数的运转成果,而这个 block 函数便是我们调用时传入的 lambda 表达式,因而其履行成果便是整个函数的成果。而also
block 函数时回来值便是 Unit ,也便是说 lambda 表达式的成果是被疏忽的。这儿能够以为调用 block 只是为了履行一项操作,而实践回来是this
再来看看为什么有时候参数是 it ,有时候又是 this 呢? 能够比照一下 also
和 apply
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
- 这儿的要害便是 block 函数的界说。 注意到 apply 中
block T.() -> Unit
的写法,能够看到这儿明确了当时函数履行的类型,一起参数为空;能够试一下,这种情况下,界说参数是没有意义的。
public fun <T> T.apply1(block: T.(Int) -> Unit): T {
block(1)
return this
}
比方这儿,虽然界说了 block 的参数为 Int 类型,可是由于应明确界说了 block 函数是在 T 类型履行,因而实践调用时也无法传递这个参数,因而这儿完成时也无法获取到详细的参数值 。
小结
Kotlin 高阶函数是平日开发中最常用的功用,使用高阶函数能够完成代码逻辑的简化和封装,最重要的一点便是把函数当参数的特性,让办法的行为能够被另外一个办法的行为控制,甚至是完成套娃。一些比较常见的三方库比方 LeakCanary/OkHttp 等使用 Kotlin 重写之后也是大量使用了高阶函数。而 let/also/apply/run/with 这几个常用的内置函数,就高阶函数的界说做了最好的师范。