美文网首页
Kotlin 高阶函数从未如此清晰(下) let/also/wi

Kotlin 高阶函数从未如此清晰(下) let/also/wi

作者: _Jun | 来源:发表于2022-05-28 14:08 被阅读0次

    前言

    上篇讲的泛型、扩展函数、内联函数是为了理解常用的高阶函数打下基础, let/also/with/run/apply/repeat 都是定义在Standard.kt 文件里,熟练掌握这些函数的使用可以大大提高我们的编码效率。接下来对这些高阶函数进行彻底分析。
    通过本篇文章,你将了解到:

    1、let 原理与使用
    2、also 原理与使用
    3、with 原理与使用
    4、run 原理与使用
    5、apply 原理与使用
    6、repeat 原理与使用
    7、总结

    为方便演示,先定义一个学生信息的基础类:

    class StudentInfo {
        //姓名
        var name:String? = "Fish"
        var alias:String ? = "小鱼人"
        //省份
        var province:String? = "北京"
        //年龄
        var age:Int ? = 18
        //性别
        var isBoy:Boolean = true
        //分数
        var score:Float = 88f
    }
    
    

    1、let 原理与使用

    原理

    public inline fun <T, R> T.let(block: (T) -> R): R {
        //内联函数,扩展函数,定义了泛型,接收一个函数类型参数
        //调用函数,返回执行结果
        return block(this)
    }
    
    

    定义了泛型T,let 作为T的扩展函数,let 参数为函数类型,接收T对象,返回R。

    使用

    不使用let

    fun testLet1(studentInfo: StudentInfo) {
        studentInfo?.isBoy = false
        studentInfo?.name = "小鱼人"
        studentInfo?.age = 14
    }
    
    

    使用let

    fun testLet2(studentInfo: StudentInfo) {
        var letRet = studentInfo?.let {
            it.isBoy = false
            it.name = "小鱼人"
            it.age = 14
            //Lambda结果作为let 返回值
            "Fish"
        }
        println("letRet:$letRet")
    }
    
    

    可以看出,简化了操作。因为let 里的函数类型只有一个参数,所以可以用it指代该参数(Lambda的约定)。

    使用let 一是可以约束变量的操作范围,二是可以在入口处统一判空。

    2、also 原理与使用

    原理

    public inline fun <T> T.also(block: (T) -> Unit): T {
        //扩展函数,函数类型入参为T对象,返回T对象
        block(this)
        //返回调用者本身
        return this
    }
    
    

    与let 类似,只是返回值有点差异。

    使用

    不使用also

    fun testAlso1(studentInfo: StudentInfo) {
        studentInfo?.isBoy = false
        studentInfo?.name = "小鱼人"
        studentInfo?.age = 14
    }
    
    

    使用also

    fun testAlso2(studentInfo: StudentInfo) {
        var letRet = studentInfo?.also {
            it.isBoy = false
            it.name = "小鱼人"
            it.age = 14
            //Lambda结果未被使用
            "Fish"
        }
        println("alsoRet:${letRet.name}")
    }
    
    

    also 返回值为调用者本身,也就是studentInfo,因此我们可以继续使用studentInfo进行操作。

    fun testAlso3(studentInfo: StudentInfo) {
        studentInfo?.also {
            it.isBoy = false
            it?.name = "小鱼人"
            it?.age = 14
            //Lambda结果作为let 返回值
            "Fish"
        }.let { 
            //继续调用
            it.score = 99f
        }
    }
    
    

    also 原理、作用与let 类似,因为其返回对象本身,因此可以用在链式调用的场景。

    3、with 原理与使用

    原理

    public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
        //内联函数,带两个参数,一个是接收者,另一个函数类型
        //T.() 泛型T的扩展函数,函数无参数
        //返回接收者调用结果,也就是Lambda返回值
        return receiver.block()
    }
    
    

    需要注意的是:with 并不是扩展函数,因此无需使用对象访问它。
    with 反编译结果:

    public static final Object with(Object receiver, @NotNull Function1 block) {
        //传入接收者对象
        return block.invoke(receiver);
    }
    
    

    T.() 表示T的扩展函数,可以表示为:block:T.()->R,可以使用receiver.block() 访问,最终实现block的函数体(Lambda)里持有T的对象,因此内部可以使用this访问T的属性和函数。

    使用

    不使用with

    fun testWith1(studentInfo: StudentInfo) {
        studentInfo?.isBoy = false
        studentInfo?.name = "小鱼人"
        studentInfo?.age = 14
    }
    
    

    使用with

    fun testWith2(studentInfo: StudentInfo) {
        var withRet = with(studentInfo) {
            isBoy = false
            name = "小鱼人"
            age = 14
            "Fish"
        }
        println("withRet:$withRet")
    }
    
    

    可以看出,使用with时只需要传入要操作的对象,而Lambda表达式里即可直接操作属性和函数。

    with 本质上是通过扩展接收者函数,内部就可以访问属性和函数(隐藏了this),通常用在需要多次书写对象调用的场景,比如ViewHolder 访问View。

    with 有个弊端:

    因为不是扩展函数,因此无法像let/also 一样在调用时通过"?"判空。

    4、run 的原理与使用

    原理

    public inline fun <T, R> T.run(block: T.() -> R): R {
        //扩展函数,函数类型也扩展了T
        return block()
    }
    
    

    run与with 类似,同样的是扩展T.(),因此在block里能够访问属性和函数。

    使用

    不使用run

    //run 使用
    fun testRun1(studentInfo: StudentInfo) {
        studentInfo?.isBoy = false
        studentInfo?.name = "小鱼人"
        studentInfo?.age = 14
    }
    
    

    使用run

    fun testRun2(studentInfo: StudentInfo) {
        var withRet = studentInfo?.run {
            isBoy = false
            name = "小鱼人"
            age = 14
            "Fish"
        }
        println("withRet:$withRet")
    }
    
    

    可以看出,run 比 with 多了可以判空的功能,并且比let 多了可以省略it访问的功能,因此:

    run 结合了let 与 with 的功能,它俩能做的run 也能做。

    5、apply 的原理与使用

    原理

    public inline fun <T> T.apply(block: T.() -> Unit): T {
        //扩展函数
        block()
        //返回调用者本身
        return this
    }
    
    

    和run 相似,只是apply 返回值为对象本身。

    使用

    不使用apply

    fun testApply1() {
        var studentInfo = StudentInfo()
        studentInfo.isBoy = false
        studentInfo.name = "小鱼人"
        studentInfo.age = 14
    }
    
    

    使用apply

    fun testApply2() {
        var applyRet = StudentInfo().apply {
            isBoy = false
            name = "小鱼人"
            age = 14
            "Fish"
        }
        println("withRet:${applyRet.name}")
    }
    
    

    apply 返回对象本身,因此我们可以在Lambda里做一些初始化操作。
    当Lambda 执行完毕后,返回的对象已经初始化完毕。

    apply 多用于对象初始化过程以及链式调用。

    6、repeat 的原理与使用

    原理

    public inline fun repeat(times: Int, action: (Int) -> Unit) {
        //循环调用action,传入参数为当前次数
        for (index in 0 until times) {
            action(index)
        }
    }
    
    

    repeat 不是扩展函数。

    使用

    fun testRepeat2() {
        var list = mutableListOf<StudentInfo>()
        repeat(10) {
            //重复这个动作10次
            list.add(StudentInfo())
            println("第 $it 个")
        }
    }
    
    

    7、总结

    let/also/with/run/apply/repeat 等高阶函数都已经分析完毕,用图总结:

    至此,Kotlin 函数的主要内容分析完毕,下篇将开启Kotlin 类与对象系列。

    本文基于Kotlin 1.5.3,文中Demo请点击

    您若喜欢,请点赞、关注,您的鼓励是我前进的动力

    相关文章

      网友评论

          本文标题:Kotlin 高阶函数从未如此清晰(下) let/also/wi

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