前言
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 这几个常用的内置函数,就高阶函数的定义做了最好的师范。
最后
为了帮助大家能更系统的学习Kotlin,在这里分享这份全网最全的Kotlin入门教程指南,有需要完整版的朋友扫描下方二维码!!!
内容展示
Kotlin入门教程指南
- 前言
1.概述
- 1.1使用 Kotlin 进行服务器端开发
- 1.2 使用 Kotlin 进行 Android 开发
- 1.3 Kotlin JavaScript 概述
- 1.4 Kotlin/Native 用于原生开发
- 1.5 用于异步编程等场景的协程
- 1.6 Kotlin 1.1 的新特性
- 1.7 Kotlin 1.2 的新特性
- 1.8 Kotlin 1.3 的新特性
2.开始
- 2.1 基本语法
- 2.2 习惯用法
- 2.3 编码规范
3.基础
- 3.1 基本类型
- 3.2 包
- 3.3 控制流:if、when、for、while
- 3.4 返回和跳转
4.类与对象
- 4.1 类与继承
- 4.2 属性与字段
- 4.3 接口
- 4.4 可见性修饰符
- 4.5 扩展
- 4.6 数据类
- …
5.函数与Lambda表达式
- 5.1 函数
- 5.2 高阶函数与lambda表达式
- 5.3 内联函数
6.其他
- 6.1 解构声明
- 6.2 集合:List、Set、Map
- 6.3 区间
- 6.4 类型的检查与转换“is”与“as”
- 6.5 This 表达式
- 6.6 相等性
- …
7.Java互操作与JavaScript
- 7.1 在 Kotlin 中调用 Java 代码
- 7.2 Java 中调用 Kotlin
- 7.3 JavaScript 动态类型
- 7.4 Kotlin 中调用 JavaScript
- 7.5 JavaScript 中调用 Kotlin
- 7.6 JavaScript 模块
- 7.7 JavaScript 反射
- 7.8 JavaScript DCE
8.协程
- 8.1 协程基础
- 8.2 取消与超时
- 8.3 通道 (实验性的)
- 8.4 组合挂起函数
- 8.5 协程上下文与调度器
- 8.6 异常处理
- 8.7 select 表达式(实验性的)
- 8.8 共享的可变状态与并发
9.工具
- 9.1 编写 Kotlin 代码文档
- 9.2 Kotlin 注解处理
- 9.3 使用 Gradle
- 9.4 使用 Maven
- 9.5 使用 Ant
- 9.6 Kotlin 与 OSGi
- 9.7 编译器插件
- 9.8 不同组件的稳定性
常见问题概述
- FAQ
- 与Java语言比较
- 与Scala比较【官方已删除】