Kolin中的作用域函数

时间:2021-6-12 作者:qvyue

原文:Kotlin官方

Kotlin中的作用域函数共有以下五种:letrunwithapply 以及 also

这些函数并没有做什么特殊的操作,只是将对当前对象的操作都集中到了一个代码块中来,写出来和看起来更加干净了而已。

不同的是这个对象在块中如何使用,以及整个表达式的结果是什么。

区别

上下文对象(this/it) 返回值
let it lambda 表达式结果
run this(隐式访问) lambda 表达式结果
with this(隐式访问) lambda 表达式结果
apply this(隐式访问) 上下文
also it 上下文

let

上下文对象作为 lambda 表达式的参数(it来访问。返回值是 lambda 表达式的结果

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

let 经常用于仅使用非空值执行代码块。如需对非空对象执行操作,可对其使用安全调用操作符 ?. 并调用 let 在 lambda 表达式中执行操作。

val str: String? = "Hello" 
//processNonNullString(str)       // 编译错误:str 可能为空
val length = str?.let { 
    println("let() called on $it")
    processNonNullString(it)      // 编译通过:'it' 在 '?.let { }' 中必不为空
    it.length
}

使用 let 的另一种情况是引入作用域受限的局部变量以提高代码的可读性。如需为上下文对象定义一个新变量,可提供其名称作为 lambda 表达式参数来替默认的 it

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")
}

// 同样的代码如果用 let() 函数来写:
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 的常见情况是对象配置。这样的调用可以理解为“将以下赋值操作应用于对象”。

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

also

上下文对象作为 lambda 表达式的参数(it来访问。 返回值是上下文对象本身。

also 对于执行一些将上下文对象作为参数的操作很有用。 对于需要引用对象而不是其属性与函数的操作,或者不想屏蔽来自外部作用域的 this 引用时,请使用 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 上下文对象

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

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

不同函数的使用场景存在重叠,你可以根据项目或团队中使用的特定约定选择函数。

尽管作用域函数是使代码更简洁的一种方法,但请避免过度使用它们:这会降低代码的可读性并可能导致错误。避免嵌套作用域函数,同时链式调用它们时要小心:此时很容易对当前上下文对象及 thisit 的值感到困惑。

takeIftakeUnless

除了作用域函数外,标准库还包含函数 takeIftakeUnless。这俩函数使你可以将对象状态检查嵌入到调用链中。

当以提供的谓词在对象上进行调用时,若该对象与谓词匹配,则 takeIf 返回此对象。否则返回 null。因此,takeIf 是单个对象的过滤函数。反之,takeUnless如果不匹配谓词,则返回对象,如果匹配则返回 null。该对象作为 lambda 表达式参数(it)来访问。

val number = Random.nextInt(100)

val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")

当在 takeIftakeUnless 之后链式调用其他函数,不要忘记执行空检查或安全调用(?.),因为他们的返回值是可为空的。

val str = "Hello"
val caps = str.takeIf { it.isNotEmpty() }?.toUpperCase()
//val caps = str.takeIf { it.isNotEmpty() }.toUpperCase() // 编译错误
println(caps)

takeIftakeUnless 与作用域函数一起特别有用。 一个很好的例子是用 let 链接它们,以便在与给定谓词匹配的对象上运行代码块。 为此,请在对象上调用 takeIf,然后通过安全调用(?.)调用 let。对于与谓词不匹配的对象,takeIf 返回 null,并且不调用 let

fun displaySubstringPosition(input: String, sub: String) {
    input.indexOf(sub).takeIf { it >= 0 }?.let {
        println("The substring $sub is found in $input.")
        println("Its start position is $it.")
    }
}

displaySubstringPosition("010000011", "11")
displaySubstringPosition("010000011", "12")

没有标准库函数时,相同的函数看起来是这样的:

fun displaySubstringPosition(input: String, sub: String) {
    val index = input.indexOf(sub)
    if (index >= 0) {
        println("The substring $sub is found in $input.")
        println("Its start position is $index.")
    }
}

displaySubstringPosition("010000011", "11")
displaySubstringPosition("010000011", "12")
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。