如何使用 Kotlin 作用域函数

作用域函数

Kotlin 提供了一系列用来在【给定对象上下文】中执行代码块的函数。
当您在提供了lambda表达式的对象上调用此类函数时,它会形成一个临时范围。
在此范围内,您可以在不使用其名称的情况下访问该对象。
常用到的作用域函数有5个:letrunwithapplyalso
基本上,这些函数都是在对象上执行代码块。不同之处在于这个对象在块内的可用性以及整个表达式返回的结果。
作用域函数不会引入任何新的功能,但它们可以使您的代码更简洁和可读。

作用域函数的差异

由于作用域函数本质上非常相似,因此理解它们之间的差异非常重要。
每个作用域函数有两个主要区别:

  • 引用上下文对象的方式
  • 返回值

上下文对象

在作用域函数的lambda内部,上下文对象可通过简短引用而不是其实际变量名来获得。
每个作用域函数可使用以下两种方法访问上下文对象:
lambda接收者(this)或lambda参数(it)

fun main() {
    val str = "Hello"
    // this
    str.run {
        println("The receiver string length: $length")
        //println("The receiver string length: ${this.length}") // does the same
    }

    // it
    str.let {
        println("The receiver string's length is ${it.length}")
    }
}

this

run,with和apply将上下文对象引用为lambda接收者 - 通过关键字 this 。因此,在他们的lambdas中,对象就像在普通类函数中一样可用。
在大多数情况下,可以在访问接收者对象的成员时省略this,从而使代码更短。另一方面,如果省略this,则可能难以区分接收者成员和外部对象或函数。
因此,建议将上下文对象作为接收者(this)用于主要在对象成员上操作的lambda:调用其函数或赋值属性。

val adam = Person("Adam").apply { 
    age = 20                       // same as this.age = 20 or adam.age = 20
    city = "London"
}

it

let,also将上下文对象作为lambda的参数。如果未指定参数名称,则通过隐式默认名称 it 访问该对象。
但是,在调用对象方法或属性时,您不能像 this 那样隐式地使用对象。
因此,当对象主要用作函数调用中的参数时,把上下文对象用作 it 更好。

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

返回值

作用域函数因返回的结果而不同:

上下文对象本身

applyalso的返回值是 上下文对象本身
因此,它们可以在调用链中用作辅助步骤:您可以继续在它们之后的同一对象上链式调用函数。

val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

它们也可以在返回上下文对象的函数的return语句中使用。

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

lambda结果

let, run, with 返回的是 lambda结果
因此,您可以在将结果赋值给变量,对结果进行链接操作等等。

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }
}
println("There are $countEndsWithE elements that end with e.")

此外,您可以忽略返回值并使用作用域函数为变量创建临时作用域。

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

作用域函数的选择

let

let 将上下文对象作为lambda的参数,返回值是 lambda结果

  • let 可用于在调用链的 结果 上调用一个或多个函数。
    eg:
      val numbers = mutableListOf("one", "two", "three", "four", "five")
      val resultList = numbers.map { it.length }.filter { it > 3 }
      println(resultList)
    使用let函数重写:
      val numbers = mutableListOf("one", "two", "three", "four", "five")
      val result = numbers.map { it.length }.filter { it > 3 }.let { 
      println(it)
      // and more function calls if needed
      } 
      println(result)
  • let 通常仅用于非空值执行代码块。
    要对非空对象执行操作,请使用安全调用操作符 ?.
      val str: String? = "Hello"   
      //processNonNullString(str)       // compilation error: str can be null
      val length = str?.let { 
          println("let() called on $it")        
          processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
          it.length
      }
  • 使用let 的另一种情况是引入作用域范围有限的局部变量,以提高代码可读性。
    为上下文对象定义一个新的变量名:
      val numbers = listOf("one", "two", "three", "four")
      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 modifications: '$modifiedFirstItem'")

with

非扩展函数:上下文对象作为参数传递,但在lambda中,它可用作接收者(this)。返回值是 lambda结果

  • 建议使用 with 来调用上下文对象上的函数,而不提供lambda结果。
    在代码中,with 可以读作“使用此对象,执行以下操作”。

      val numbers = mutableListOf("one", "two", "three")
      with(numbers) {
          println("'with' is called with argument $this")
          println("It contains $size elements")
      }
  • with的另一个用途是引入一个辅助对象,其属性或函数将用于计算值。

      val numbers = mutableListOf("one", "two", "three")
      val firstAndLast = with(numbers) {
          "The first element is ${first()}," +
              " the last element is ${last()}"
      }
      println(firstAndLast)

run

上下文对象可用作接收者(this)。返回值是 lambda结果
run 执行与 with 相同的操作,但是调用 let - 作为上下文对象的扩展函数。

  • 当lambda包含对象初始化和返回值的计算时,run 非常有用。

      val service = MultiportService("https://example.kotlinlang.org", 80)
    
      val result = service.run {
          port = 8080
          query(prepareRequest() + " to port $port")
      }
    
      // the same code written with let() function:
      val letResult = service.let {
          it.port = 8080
          it.query(it.prepareRequest() + " to port ${it.port}")
      }
  • 除了在接收者对象上调用run之外,您还可以将其用作 非扩展函数。非扩展 run 允许您在需要表达式的地方执行由多个语句组成的块。

      val hexNumberRegex = run {
          val digits = "0-9"
          val hexDigits = "A-Fa-f"
          val sign = "+-"
    
          Regex("[$sign]?[$digits$hexDigits]+")
      }
    
      for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
          println(match.value)
      }

apply

上下文对象可用作接收者(this)。返回值是对象本身

  • 对于不返回值的代码块使用 apply,并且主要对接收器对象的成员进行操作。
    apply 用于对象的配置。这样的调用可以理解为“将以下赋值应用于对象”。
      val adam = Person("Adam").apply {
          age = 32
          city = "London"        
      }
    将接收者作为返回值,您可以轻松地将 apply 包含到调用链中,以便进行更复杂的处理。

also

上下文对象可用作参数(it)。返回值是对象本身

also 适用于执行将上下文对象作为参数的某些操作。
对于不更改对象的附加操作,如日志记录或打印调试信息,请使用 also
通常,您可以在不破坏程序逻辑的情况下从调用链中删除 also 的调用。

当您在代码中看到 also 时,您可以将其读作“并执行以下操作”。

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")

作用域函数主要差异表

作用域函数 对象引用 返回值 是扩展函数
let it lambda结果
run this lambda结果
run - lambda结果 否:没有上下文对象调用
with this lambda结果 否:将上下文对象作为参数。
apply this 上下文对象
also it 上下文对象

以下是根据预期用途选择作用域函数的简要指南:

  • 在非空对象上执行lambda:let
  • 将表达式作为局部作用域中的变量引入:let
  • 对象配置:apply
  • 对象配置和计算结果:run
  • 在需要表达式的地方运行语句:非扩展 run
  • 附加效果: also
  • 在对象上对函数进行分组调用:with

虽然作用域函数是使代码更简洁的一种方法,但是要避免过度使用它们:它会降低代码的可读性并导致错误。
避免嵌套作用域函数,并在链接它们时要小心:很容易混淆当前上下文对象 和 this / it 的值。


   转载规则


《如何使用 Kotlin 作用域函数》 congwiny 采用 知识共享署名 4.0 国际许可协议 进行许可。
评论
 上一篇
Kotlin反射的使用 Kotlin反射的使用
反射反射(Reflection)是程序的自我分析能力,通过反射机制能够动态读取一个类的信息,可以确定类中有哪些函数、构造函数以及属性,并且能够在运行时动态加载类。 在Kotlin中有两种方式来实现反射的功能。一种是调用Java的反射包jav
2019-08-30
下一篇 
Kotlin与Java混合编程 Kotlin与Java混合编程
Kotlin与Java 混合编程Kotlin与Java的类型映射Kotlin 对 Java基本数据类型的映射Kotlin 特殊处理一部分 Java 类型。这样的类型不是“按原样”从 Java 加载,而是 映射 到相应的 Kotlin 类型。
2019-08-29
  目录