Kotlin 范围函数(Scope Funtions)

1,303 阅读3分钟

概述

范围函数的目的是为了在一个代码块中持有上下文下对象并执行该代码块。完全由标准库提供,让代码更简洁和易读。此类型的函数共有五个letrunwithapplyaslo
let为例:

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.name = "Jack"
    it.age = 21
    println(it)
}

传统写法:

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.name = "jack"
alice.age = 21
println(alice)

这几个范围函数它们之间的主要区别主要有两点:

  1. 引用上下文对象的方式;
  2. 函数的返回值;

上下文对象(context object

  1. 带有接受者的函数字面值:thisrunwithapply
  2. 作为lambda表达式的单个参数的隐式名称:itletalso

通过itthis在代码块中访问上下文对象,this可以被省略,it不能被省略。

返回值(return value

  • applyalso返回上下文对象,
  • letrunwith返回lambda函数的返回值(使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值)

let

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

代码块中通过it访问上下文对象,返回值是lambda值。 如果代码快中只包含一个it作为参数的单个函数,可以使用方法引用(::)代替lambda

var numbers = mutableListOf("one", "two", "three", "four", "five")
fun main() {
    numbers.map { it.length }.filter { it > 3 }.let { print(it) }
}

let 的常见用法:

  1. 通过安全操作符?.,使用let函数处理需要针对一个可null的对象统一做判空处理。
val str: String? = "Hello"   
//processNonNullString(str)       // compilation error: str can be null
val length = str?.let { 
    println("let() called on $it")        
    it.length
}
  1. 引入局部变量来改善代码的可读性。将lambda函数的默认参数it变换成firstItem
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

with this object, do the following

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

代码块中通过this访问上下文对象,返回lambda值。
推荐用法:

  • 访问上下文对象的方法。而不接受返回值。
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

run

public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

代码块中通过this访问上下文对象,返回lambda值。
适用于let,with函数任何场景,避免了with函数无法做空安全检测的问题,可以像let函数使用?.一样做非空判断。也可以解决let函数只能显示通过it来调用上下文对象的方法,而通过隐示的this来访问。

apply

apply the following assignments to the object.

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

代码块中通过this来访问上下文对象,返回对象本身。 apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。

also

and also do the following

public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

代码块中通过it访问上下文对象,返回值为对象本身。
使用方式与let类似,区别是返回值对象本身。

总结

函数名 上下文对象 返回值 是否拓展函数
let it lambda result yes
with this lambda result no
run this lambda result yes
run - lambda result no
apply this context object yes
also it context object yes

参考链接