Kotlin的run、let、also和apply的用法

公司的Web框架逐渐换成了自研的Kotlin框架,臃肿的Java代码得到极大的改善。在Kotlin世界里面,脚本式的运行环境很容易导致局部变量被污染,所以Kotlin内置了很多局部函数来辅助。

run

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}

run提供了一个闭包,使得内部变量可以与外部变量不产生命名冲突。

run {
    if (firstTimeView) introView else normalView //动态决定不同View
}.show()

run支持返回最后一个表达式的值,利用这个特性可以在run里面完成类似动态选择的功能。

with

webview.settings.javaScriptEnabled = true
webview.settings.databaseEnabled = true

//使用with,上面代码可以简化成下面样式

with(webview.settings) {
    javaScriptEnabled = true
    databaseEnabled = true
}

with提供了一个隐藏对象,从而简化了对这个对象处理的臃肿代码。

其实run也支持类似with的功能:

webview.settings.run{
    javaScriptEnabled = true
    databaseEnabled = true
}

既然with和run都有类似的功能,在什么情况下需要选择run呢?

//丑
with(webview.settings) {
      this?.javaScriptEnabled = true
      this?.databaseEnabled = true
}

//爽
webview.settings?.run {
    javaScriptEnabled = true
    databaseEnabled = true
}

面对not-null的情况,object?.run{ }用起来会比with好用很多。实际项目里面,run的使用频率也是更高的。

run vs let

run和let在大部分时候用途都是非常接近的,例如下面例子:

user?.run{
    //user ==> this
    println("my name is $name)
}

user?.let{
    //user ==> it
    println("my name is $it.name")
}

虽然看起来没啥特别大的区别,let实际上提供了更好语义说明语法,例如下面:

loginedUser.id?.let {
      userId ->
      User.findById(userId)
}

loginedUser.id?.run {
    User.findById(this) //this指的是啥?不仔细琢磨还看不出来
}

let vs also

let和also在使用方面,非常像,对象都是默认简化成it

stringVariable?.let {
      println("The length of this String is ${it.length}")
}
// Exactly the same as below
stringVariable?.also {
      println("The length of this String is ${it.length}")
}

let和also真正的区别在于返回值不同:

val original = "abc"

original.let{
    println(it) //abc
    it.reversed()
}.let{
    println(it) //cba
}

original.also{
    println(it) //abc
    it.reversed()
}.also{
    println(it) //abc
}

let每次会返回最后一个表达式的结果,而also会返回内置对象。

let和also经常会搭配一起使用,例如下面:

//java逻辑应该是写成这样子的
fun makeDir(path: String): File  {
    val result = File(path)
    result.mkdirs()
    return result
}
//在kotlin里面,使用let和also可以简写
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

apply

apply的作用如下:

obj.run{
    name = "hello"
    this
}
//以上的代码,可以简写为下面
obj.apply{
    name = "hello"
}

其实就是在run最后一行强行塞入this。

下面看看实战作用:

//java逻辑应该是写成这样子的
fun createIntent(intentData: String, intentAction: String): Intent {
    val intent = Intent()
    intent.action = intentAction
    intent.data=Uri.parse(intentData)
    return intent
}
//在kotlin里面,使用apply可以简写
fun createIntent(intentData: String, intentAction: String) =
        Intent().apply { action = intentAction }
                .apply { data = Uri.parse(intentData) }

总结

不管是run、with、also还是apply,最终的目的还是提供了一个简化代码的语法糖。老实说,相比起java,理解起来费劲很多很多。