前言

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。

  • 参数

Kotin 语法糖

Kotin 语法糖

其次从 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 高阶函数的特性。可是他又是怎么完成这些微妙的差异的那?我们能够比照一下 letalso

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 呢? 能够比照一下 alsoapply

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 这几个常用的内置函数,就高阶函数的界说做了最好的师范。