美文网首页M2_Androidkotlin
kotlin之let、with、run、apply、also

kotlin之let、with、run、apply、also

作者: 0246eafe46bd | 来源:发表于2021-11-26 16:09 被阅读0次

    总结

    let、with、run、apply、also 这几个的作用就是在一个对象的上下文中执行一段代码。当我们使用 lambda 表达式在一个对象上调用这样的一个函数时,它就形成了一个暂时的域,在这个域中,可以在不使用变量名的情况下获取到对象,因此,这些函数也被称为作用域函数(Scope functions)

    run、with 和 apply 的上下文对象是 this ,可以省略掉 this 单词,因此主要用于操作对象成员(例如调用对象的方法或使用其属性)的时候

    let 和 also 的上下文对象是 it , 适用于将此对象作为方法调用参数传入

    apply 和 also 返回上下文对象,可用于链式调用返回上下文对象的函数的返回语句

    let、run 和 with 返回 lambda 结果,可以在将结果分配给变量时使用

    方法 上下文对象 返回值 扩展函数 使用场景
    let it lambda 结果 在非空对象上执行一个 lambda或者在局部域中引入一个变量
    with this lambda 结果 在一个对象上组合函数调用
    run this lambda 结果 对象配置并计算结果
    apply this 上下文对象 对象配置
    also it 上下文对象 额外的效果
    非扩展函数 run lambda 结果 在需要表达式的地方运行语句

    表格里的上下文对象中,this 是可以省略的, it 是隐含的默认参数名,可以显式地指定为其他名字

    let

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

    可以看到第一句代码是 Contract ,这就需要引出 Contract 的内容

    Contract 示例

    fun String?.isNull(): Boolean {
        return this == null
    }
    
    fun test() {
        var str: String? = null
    
        if (str.isNull()) {
            str = "kotlin contract"
        }
    
        println(str.length)
    }
    

    如上的代码中,先为 String? 定义一个扩展函数 isNull(),用于判断是否为 null,在 test 函数中,声明一个 String? 类型的 str ,使用 isNull() 判断,如果其为 null ,就为其赋值,这样,在下面调用 str.length 的时候 str 就一定不会为 null ,但实际上,这里还是会编译报错:

    Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

    这是因为编译器无法分析每个函数,不能得到 str 不为 null 的结果,也就无法将 String? 智能转换为 String ,Contract 就可以解决问题,它可以向编译器通知函数行为,上面的代码修改为如下,就不会报错了

    @ExperimentalContracts
    fun String?.isNull(): Boolean {
        //下面是添加的内容
        contract {
            returns(false) implies (this@isNull != null)
        }
        //上面是添加的内容
        return this == null
    }
    
    @ExperimentalContracts
    fun test() {
        var str: String? = null
    
        if (str.isNull()) {
            str = "kotlin contract"
        }
    
        println(str.length)
    }
    

    上面代码中添加内容是告诉编译器:如果返回值为 false ,那么 this(函数的接收者)不为 null

    Contract 的概念

    Contract 是一种向编译器通知函数行为的方法,有以下特点:

    1. 只能在 top-level 函数体内使用 Contract
    2. Contract 所调用的声明必须是函数体内第一条语句
    3. Kotlin 编译器并不会验证 Contract,因此必须编写正确合理的 Contract
    4. 内联化的函数(也需要是 top-level 层级的函数)支持使用 Contract

    Contract 的分类

    Returns Contracts

    表示当 return 的返回值是某个值(例如true、false、null)时,implies 后面的条件成立,有以下几种形式:

    形式 说明
    returns(value: Any?) implies 条件 如果函数返回值为 value,条件成立
    returns() implies 条件 如果函数能够正常返回,且没有抛出异常,条件成立
    returnsNotNull implies 条件 如果函数返回非 null 值,条件成立

    CallsInPlace Contracts

    CallsInPlace Contracts 允许开发者对调用的 lambda 表达式进行频率上的约束,只能在 inline 函数中调用

    前面的高阶函数 let 就是一个 CallsInPlace Contracts

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

    contract() 中的 callsInPlace 会告诉编译器,lambda 表达式 block 在 let 函数内只会执行一次

    callsInPlace() 中的 InvocationKind 是一个枚举类,包含如下的枚举值

    枚举值 说明
    AT_MOST_ONCE 函数参数调用次数 <= 1
    EXACTLY_ONCE 函数参数调用次数 == 1
    AT_LEAST_ONCE 函数参数调用次数 >= 1
    UNKNOWN 函数参数调用次数 不限制

    以上 contract 内容就结束了,下面看 let 函数的实现,首先要明白两点:

    1. 在扩展函数内部,你可以像成员函数那样使用 this 来引用接收者对象
    2. 当 lambda 表达式只有一个参数,可以用 it 关键字来引用唯一的实参

    源码

    上面的源代码里可以看到:

    let 函数是类型 T 的扩展函数,返回类型为 R,只有一个参数,即 block,类型为 (T) -> R,指代 参数为 T ,返回值为 R 的函数,因此上下文对象为 it ,指代 block 的唯一参数 T 。返回值为 block(this) ,是调用 block 指代的函数,并返回 block 函数的返回值,也就是返回 lambda 的结果

    示例

    在非空对象上执行一个 lambda

    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
    }
    

    为了提高可读性,在局部域中引入一个变量

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

    with

    源码

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

    with 函数不是扩展函数,而是将类型 T 的一个对象 receiver 作为参数传入 with 函数,同时传入 block 参数,block 的类型为 T.() -> R ,即 类型 T 的一个无参且返回值为类型 R 的扩展函数,而 block 函数的调用者就是前面传入的 receiver 参数,因此上下文对象为 this ,指代扩展函数的接收者 receiver ,返回值为 receiver.block() ,是调用 block 指代的函数,并返回 block 函数的返回值,也就是返回 lambda 的结果

    示例

    推荐使用情况:调用对象的方法和属性,但不返回结果。意味着 “with this object, do the following

    val numbers = mutableListOf("one", "two", "three")
    with(numbers) {
        println("'with' is called with argument $this")
        println("It contains $size elements")
    }
    

    使用对象的属性或方法计算出一个结果

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

    run

    run 有两个,一个是扩展函数 run ,一个是非扩展函数 run

    源码

    扩展函数 run

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

    run 函数是类型 T 的一个扩展函数,返回值类型为 R,只有一个参数,即 block,类型为 T.() -> R ,即 T 的一个无参且返回值类型为 R 的扩展函数,因此上下文对象为 this ,指代调用 block 扩展函数的接收者,也就是调用 run 函数的对象,返回值为 block() ,是调用 block 指代的函数,并返回 block 函数的返回值,也就是返回 lambda 的结果

    非扩展函数 run

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

    run 函数不是扩展函数,返回类型为 R , run 函数的唯一参数 block 的类型为 () -> R ,即 一个无参且返回类型为 R 的函数类型,因此 没有上下文对象,返回值为 block() ,是调用 block 指代的函数,并返回 block 函数的返回值,也就是返回 lambda 的结果

    示例

    扩展函数 run

    同时包含对象初始化和返回值的计算

    val service = MultiportService("https://example.kotlinlang.org", 80)
    
    val result = service.run {
        port = 8080
        query(prepareRequest() + " to port $port")
    }
    

    非扩展函数 run

    需要返回值的情况下执行由多个语句组成的块

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

    apply

    源码

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

    apply 函数是类型 T 的一个扩展函数,且其返回值类型为 T 类型,唯一的参数 block 的类型为 T.() -> Unit,即 T 的一个无参无返回值扩展函数,因此上下文对象为 this ,指代调用 block 扩展函数的接收者,也就是调用 apply 函数的对象 ,返回值为 this,也就是调用者,即返回上下文对象

    示例

    对象配置。意味着 “apply the following assignments to the object

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

    also

    源码

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

    also 函数是类型 T 的一个扩展函数,且其返回值类型为 T 类型,block 参数的类型为 (T) -> Unit,即 参数为 T 且无返回值的函数,因此上下文对象为 it ,指代 block 的唯一参数 T ,返回值为 this,也就是调用者,即返回上下文对象

    示例

    将上下文作为参数。意味着 "and also do the following with the object"

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

    相关文章

      网友评论

        本文标题:kotlin之let、with、run、apply、also

        本文链接:https://www.haomeiwen.com/subject/rqqqxrtx.html