Kotin 语法糖

前言

Kotlin 语法糖的总结和原理分析。

Kotlin 有很多实用的语法糖,比如扩展函数、object 单例、apply/run/with 等内置函数,对于开发者来说非常的友好的方便。简单梳理和总结包括但不限于上述这些语法糖的内容。

Syntactic Sugar

内置函数

kotlin-stdlib 内的 Standard.kt 文件内定义了几个比较实用的顶层函数 比如 apply/with/run/let/also 等,这几个函数的功能比较相似,但又略微有些差异,在此梳理一下。

  • 示例
kotlin 复制代码
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

  • 返回值
shell 复制代码
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 高阶函数的特性。但是他又是如何实现这些微妙的差异的那?我们可以对比一下 letalso

ko 复制代码
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

ko 复制代码
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 的写法,可以看到这里明确了当前函数执行的类型,同时参数为空;可以试一下,这种情况下,定义参数是没有意义的。
k 复制代码
  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 这几个常用的内置函数,就高阶函数的定义做了最好的师范。

相关推荐
Hx_Ma164 小时前
SpringMVC框架提供的转发和重定向
java·开发语言·servlet
期待のcode5 小时前
原子操作类LongAdder
java·开发语言
舟舟亢亢5 小时前
Java集合笔记总结
java·笔记
小酒窝.6 小时前
【多线程】多线程打印ABC
java
2601_949833396 小时前
flutter_for_openharmony口腔护理app实战+预约管理实现
android·javascript·flutter
乡野码圣6 小时前
【RK3588 Android12】RCU机制
java·jvm·数据库
JAVA+C语言6 小时前
如何优化 Java 多主机通信的性能?
java·开发语言·php
编程彩机7 小时前
互联网大厂Java面试:从分布式架构到大数据场景解析
java·大数据·微服务·spark·kafka·分布式事务·分布式架构
小酒窝.8 小时前
【多线程】多线程打印1~100
java·多线程
君爱学习8 小时前
基于SpringBoot的选课调查系统
java