data class Person(var name: String, var age: Int, var city: String) {
fun moveTo(newCity: String) { city = newCity }
fun incrementAge() { age++ }
}
fun main() {
//sampleStart
Person("Alice", 20, "Amsterdam").let {
println(it)
it.moveTo("London")
it.incrementAge()
println(it)
}
//sampleEnd
}
如果不使用 let 函数, 为了实现同样的功能, 你就不得不引入一个新的变量, 并在每次用到它的时候使用变量名来访问它.
data class Person(var name: String, var age: Int, var city: String) {
fun moveTo(newCity: String) { city = newCity }
fun incrementAge() { age++ }
}
fun main() {
//sampleStart
val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)
//sampleEnd
}
data class Person(var name: String, var age: Int = 0, var city: String = "")
fun main() {
//sampleStart
val adam = Person("Adam").apply {
age = 20 // 等价于 this.age = 20
city = "London"
}
println(adam)
//sampleEnd
}
使用 it
let 和 also 函数使用另一种方式, 它们将上下文对象作为 Lambda 表达式的 参数 来访问. 如果参数名称不指定, 那么上下文对象使用隐含的默认参数名称 it. it 比 this 更短, 而且带 it 的表达式通常也更容易阅读.
但是, 你就不能象省略 this 那样, 隐含地访问访问对象的函数和属性. 因此, 通过 it 访问上下文对象的方式, 比较适合于对象主要被用作函数参数的情况. 如果你的代码段中存在多个变量, it 也是更好的选择.
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
//sampleStart
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
println(i)
//sampleEnd
}
下面的示例通过有名称的 Lambda 参数 value 来访问上下文对象.
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
//sampleStart
fun getRandomInt(): Int {
return Random.nextInt(100).also { value ->
writeToLog("getRandomInt() generated value $value")
}
}
val i = getRandomInt()
println(i)
//sampleEnd
}
apply 和 also 的返回值是作用域对象本身. 因此它们可以作为 旁路(side step) 成为链式调用的一部分: 你可以在这些函数之后对同一个对象继续调用其他函数.
fun main() {
//sampleStart
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()
//sampleEnd
println(numberList)
}
还可以用在函数的 return 语句中, 将上下文对象作为函数的返回值.
import kotlin.random.Random
fun writeToLog(message: String) {
println("INFO: $message")
}
fun main() {
//sampleStart
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
val i = getRandomInt()
//sampleEnd
}
返回 Lambda 表达式的结果值
let, run, 和 with 函数返回 Lambda 表达式的结果值. 因此, 如果需要将 Lambda 表达式结果赋值给一个变量, 或者对 Lambda 表达式结果进行链式操作, 等等, 你可以使用这些函数.
fun main() {
//sampleStart
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.")
//sampleEnd
}
此外, 你也可以忽略返回值, 只使用作用域函数来为局部变量创建一个临时的作用域.
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
val firstItem = first()
val lastItem = last()
println("First item: $firstItem, last item: $lastItem")
}
//sampleEnd
}
let 函数可以用来在链式调用的结果值上调用一个或多个函数. 比如, 下面的代码对一个集合执行两次操作, 然后打印结果:
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList)
//sampleEnd
}
使用 let 函数, 可以改写上面的示例, 使得不必将 List 操作的结果赋值给一个变量:
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let {
println(it)
// 如果需要, 还可以调用更多函数
}
//sampleEnd
}
如果传递给 let 的 Lambda 表达式的代码段只包含唯一的一个函数调用, 而且使用 it 作为这个函数的参数, 那么可以使用方法引用 (::) 来代替 Lambda 表达式:
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let(::println)
//sampleEnd
}
let 经常用来对非 null 值执行一段代码. 如果要对可为 null 的对象进行操作, 请使用 null 值安全的调用操作符 ?., 然后再通过 let 函数, 在 Lambda 表达式内执行这段操作.
fun processNonNullString(str: String) {}
fun main() {
//sampleStart
val str: String? = "Hello"
//processNonNullString(str) // 编译错误: str 可能为 null
val length = str?.let {
println("let() called on $it")
processNonNullString(it) // OK: 在 '?.let { }' 之内可以保证 'it' 不为 null
it.length
}
//sampleEnd
}
你也可以使用 let 函数, 在一个比较小的作用域内引入局部变量, 让你的代码更加易读. 为了对上下文对象定义一个新的变量, 请将变量名作为 Lambda 表达式的参数, 然后就可以在 Lambda 表达式使用这个参数名, 而不是默认名称 it.
fun main() {
//sampleStart
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 + "!"
}.uppercase()
println("First item after modifications: '$modifiedFirstItem'")
//sampleEnd
}
with 函数
上下文对象 通过接受者 (this) 访问.
返回值 是 Lambda 表达式的结果值.
由于 with 不是一个扩展函数: 上下文对象通过参数传递, 但在 Lambda 表达式内部, 可以作为接受者 (this) 访问.
我们推荐使用 with 函数的情况是, 你可以用它在上下文对象上调用函数, 但不需要使用返回值. 在代码中, with 可以被理解为 " 使用这个对象, 进行以下操作. "
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println("'with' is called with argument $this")
println("It contains $size elements")
}
//sampleEnd
}
你也可以使用 with 函数, 引入一个辅助对象, 使用它的属性或函数来计算得到一个结果值.
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
"The first element is ${first()}," +
" the last element is ${last()}"
}
println(firstAndLast)
//sampleEnd
}
run 函数
上下文对象 是接受者 (this).
返回值 是 Lambda 表达式的结果值.
run 的功能与 with 一样, 但它作为扩展函数来实现. 因此和 let 一样, 你可以对上下文对象使用点号来调用它.
如果你的 Lambda 表达式既初始化对象, 也计算结果值, 那么就很适合使用 run 函数.
class MultiportService(var url: String, var port: Int) {
fun prepareRequest(): String = "Default request"
fun query(request: String): String = "Result for query '$request'"
}
fun main() {
//sampleStart
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}")
}
//sampleEnd
println(result)
println(letResult)
}
你也可以把 run 作为非扩展函数来使用. 非扩展函数版本的 run 函数没有上下文对象, 但它仍然返回 Lambda 表达式的结果. 通过使用非扩展函数方式的 run 函数, 你可以在需要表达式的地方执行多条语句的代码段. 在代码中, 非扩展函数方式的 run 函数可以看作是 " 执行这个代码段, 并计算结果 ".
fun main() {
//sampleStart
val hexNumberRegex = run {
val digits = "0-9"
val hexDigits = "A-Fa-f"
val sign = "+-"
Regex("[$sign]?[$digits$hexDigits]+")
}
for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
println(match.value)
}
//sampleEnd
}
data class Person(var name: String, var age: Int = 0, var city: String = "")
fun main() {
//sampleStart
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
//sampleEnd
}
apply 的另一种使用场景是, 将 apply 函数用作链式调用的一部分, 用来实现复杂的处理.
also 函数
上下文对象 是 Lambda 表达式的参数 (it).
返回值 是对象本身.
also 函数适合于执行一些将上下文对象作为参数的操作. 如果需要执行一些操作, 其中需要引用对象本身, 而不是它的属性或函数, 或者如果你不希望覆盖更外层作用域(scope)中的 this 引用, 那么就可以使用 also 函数.
如果在代码中看到 also 函数, 可以理解为 " 对这个对象还执行以下操作 ".
fun main() {
//sampleStart
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
//sampleEnd
}
import kotlin.random.*
fun main() {
//sampleStart
val number = Random.nextInt(100)
val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")
//sampleEnd
}
fun main() {
//sampleStart
val str = "Hello"
val caps = str.takeIf { it.isNotEmpty() }?.uppercase()
//val caps = str.takeIf { it.isNotEmpty() }.uppercase() // 这里会出现编译错误
println(caps)
//sampleEnd
}
takeIf 函数和 takeUnless 函数在与作用域函数组合使用时特别有用. 例如, 你可以将 takeIf 和 takeUnless 函数与 let 函数组合起来, 可以对满足某个条件的对象运行一段代码. 为了实现这个目的, 可以先对这个对象调用 takeIf 函数, 然后使用 null 值安全方式(?. )来调用 let 函数. 对于不满足检查条件的对象, takeIf 函数会返回 null, 然后 let 函数不会被调用.
fun main() {
//sampleStart
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")
//sampleEnd
}
fun main() {
//sampleStart
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")
//sampleEnd
}