Kotlin 中的 let, run, with, apply, also 函数,你区分得清楚吗?

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/willway_wang/article/details/99689485

1. 前言

学习使用 Kotlin 有一段时间了。对于常用的几个函数:letrunwithapplyalso 却不是非常清楚了解。
本文主要内容包括:介绍 letrunwithapplyalso 函数的用法,以及它们之间的区别;并说明在实际开发中如何选用。

2. 正文

这几个函数都是包含在 Kotlin 的标准库中,它们的唯一目的就是在一个对象的上下文中执行一段代码。当我们使用提供的 lambda 表达式在一个对象上调用这样的一个函数时,它就形成了一个暂时的域。在这个域中,我们可以在不使用变量名的情况下获取到对象。所以,这些函数也被称为域函数。
这些函数做的事情基本上是一样的:在一个对象上执行一段代码。不同之处在于如何引用上下文对象(是 this 还是 it以及整个表达式的结果是什么

2.1 上下文对象(context object):this 还是 it

在域函数的 lambda 内部,上下文对象不是通过它实际的变量名来获取的,而是通过一个短的引用来获取的。具体来说有两种方式:

  • this,作为 lambda 的接受者;
  • it,作为 lambda 的参数。

需要说明的是 this 是可以省略的;it 是隐含的默认参数名,可以显式地指定其他名字。
官方文档上推荐:使用 this,用来操作对象成员,如调用函数或者给属性赋值;使用 it,作为方法调用的参数。
runwithapply 函数引用上下文对象为 this;而 letalso 函数把上下文对象作为 it
请看下面的例子:

fun main(args: Array<String>) {
    val hello = "Hello, World!"

    // this
    hello.run { println("The receiver string's length is $length") }
    // 这行与上面是等效的
    hello.run {  println("The receiver string's length is ${this.length}") }
    // it
    hello.let { println("The receiver string's length is ${it.length}") }
}

/*
Result:
The receiver string's length is 13
The receiver string's length is 13
The receiver string's length is 13
*/

2.2 返回值

这些域函数的返回值也是有区分的,具体来看一下:

  • applyalso:返回上下文对象;
  • letwithrun:返回 lambda 结果。

下面具体来看每一个函数:

2.3 let

上下文对象作为 it 来使用,返回值是 lambda 结果,它是一个扩展函数。
看一个例子:

data class Person1(var name: String, var age: Int, var city: String) {
    fun moveTo(newCity: String) {
        city = newCity
    }

    fun incrementAge() {
        age++;
    }
}

fun main(args: Array<String>) {
    // 使用 let 函数
    Person1("Alice", 20, "Amsterdam").let {
        println(it)
        it.moveTo("London")
        it.incrementAge()
        println(it)
    }
    // 不使用 let 函数
    val person1 = Person1("Alice", 20, "Amsterdam")
    println(person1)
    person1.moveTo("London")
    person1.incrementAge()
    println(person1)
}

上面的例子:通过使用 let 函数,省去了一个变量的声明。

  • let 实际使用案例1:和安全调用运算符(?.)结合使用,这是一种很典型的应用。
fun processNotNullString(str: String) {}
fun main(args: Array<String>) {
    val str: String? = "hello"
    val length = str?.let {
        println("let() called on $it")
        it.length
    }
}
  • let 实际使用案例2:在有限的域内引入一个局部变量,来改变代码可读性。
fun main(args: Array<String>) {
    // do not use let
    val numbers1 = listOf("one", "two", "three", "four", "five")
    var first = numbers1.first()
    println("The first item of the list is '$first'")
    first = if (first.length > 5) first else "!$first!"
    val modifiedFirst = first.toUpperCase()
    println("First item after modification: '$modifiedFirst'")
    println("------------------------------------------------------------")
    // use let
    val numbers = listOf("one", "two", "three", "four", "five")
    val modifiedFirstItem = numbers.first().let { firstItem ->
        println("The first item of the list is '$firstItem'")
        if (firstItem.length > 5) firstItem else "!$firstItem!"
    }.toUpperCase()
    println("First item after modification: '$modifiedFirstItem'")
}

/*
The first item of the list is 'one'
First item after modification: '!ONE!'
------------------------------------------------------------
The first item of the list is 'one'
First item after modification: '!ONE!'
*/

2.4 with

上下文对象是作为 with 函数的参数传入的。在 lambda 内部,上下文对象作为 this 来使用,返回值是 lambda 结果,它不是一个扩展函数。

  • with 实际使用案例1:在这个对象上调用它自己的函数和属性,而不提供 lambda 结果。可以这样理解:带着这个对象,做如下的操作。
    下面的例子是 Android 中在 Adapter 中绑定数据和视图的代码,这也是一个典型使用场景
data class DownloadInfo(
    val fileName: String,
    var select: Boolean = false
)
fun bindItem(downloadInfo: DownloadInfo, position: Int) {
            with(downloadInfo) {
                itemView.tv_url.text = fileName
                itemView.cb_download_url.isChecked = select
            }
}
  • with 实际使用案例2:引入一个辅助对象,它的属性和方法会被用于计算一个值。
fun main(args: Array<String>) {
    val numbers = mutableListOf("one", "two", "three")
    val firstAndLast = with(numbers) {
       "The first element is ${first()},"+
               " the last element is ${last()}"
    }
    println(firstAndLast)
}

2.5 run

run 函数可以是扩展函数,也可以不是扩展函数。

作为扩展函数时,在 lambda 内部,上下文对象作为 this 来使用,返回值是 lambda 结果。
run 函数做的事情和 with 函数是一样的,调用起来和 let 函数是一样的。

  • run 实际使用案例:在 lambda 内部,包含对象初始化以及返回值的计算。
class MultiportService(var url: String, var port: Int) {
    fun prepareRequest(): String = "Default request"
    fun query(request: String): String = "Result from query '$request'"
}

fun main(args: Array<String>) {
    val service = MultiportService("https://example.kotlinlang.org", 80)
    // use run,和 let 调用方式一样,lambda 内部和 with 是一样的
    val result = service.run {
        port = 8080
        query(prepareRequest() + " to port $port")
    }
    // use let
    val letResult = service.let {
        it.port = 8080
        it.query(it.prepareRequest() + " to port ${it.port}")
    }
    // use with
    val withResult = with(service) {
        port = 8080
        query(prepareRequest() + " to port $port")
    }
    println(result)
    println(letResult)
    println(withResult)
}
/*
Result:
Result from query 'Default request to port 8080'
Result from query 'Default request to port 8080'
Result from query 'Default request to port 8080'
*/

作为非扩展函数时,返回结果还是 lambda 结果,但是没有接收对象了。简言之,就是运行一段代码而已。

2.6 apply

在 lambda 内部,上下文对象作为 this 来使用,返回值是 对象本身,它是一个扩展函数。
apply 的典型应用是对象配置,可以这样说把如下的操作应用在这个对象上。

data class Person2(var name: String, var age: Int = 0, var city: String = "")

fun main(args: Array<String>) {
    val adam = Person2("Adam").apply {
        age = 32
        city = "London"
    }
    println(adam)
}

实际开发案例,配置 Intent

fun play(context: Context, url: String, title: String){
    val intent = Intent(context, PlayerActivity::class.java).apply {
        putExtra(INTENT_URL, url)
        putExtra(INTENT_TITLE, title)
    }
    context.startActivity(intent)
}

2.7 also

在 lambda 内部,上下文对象作为 it 来使用,返回值是 对象本身,它是一个扩展函数。
also 用于把上下文对象作为参数来执行一些操作。这些是不会改变上下文对象的操作,例如打印日志,给其他变量赋值。
双重校验锁的单例写法的例子:

class TabNoteRepository {
    companion object {
        @Volatile
        private var instance: TabNoteRepository? = null
        fun getInstance() =
            instance ?: synchronized(this) {
                instance ?: TabNoteRepository().also { instance = it}
            }
    }
}

3. 最后

上面虽然对 letrunwithapplyalso 函数一一作了介绍,但是在实际开发中,还是很容易混淆使用的。所以,这里把它们之间的关键区别放在表里:

Scope function Object reference Return value is extension function?
let it lambda result Yes
run this lambda result Yes
run - lambda result No
with this lambda result No
apply this object itself Yes
also it object itself Yes

官方文档也给出一些使用指导:

  • 在非空对象上执行一个 lambda:let
  • 在局部域中引入一个表达式作为变量:let
  • 对象配置:apply
  • 对象配置并计算结果:run
  • 额外的效果:also
  • 在一个对象上组合函数调用:with

参考

1.https://kotlinlang.org/docs/reference/scope-functions.html

猜你喜欢

转载自blog.csdn.net/willway_wang/article/details/99689485