Kotlin 高阶函数从未如此清晰(下) let/also/with/run/apply/repeat 一看就会

前言

高阶函数系列文章:

Kotlin 高阶函数从未如此清晰(上)
Kotlin 高阶函数从未如此清晰(中)
Kotlin 高阶函数从未如此清晰(下) let/also/with/run/apply/repeat 一看就会

上篇讲的泛型、扩展函数、内联函数是为了理解常用的高阶函数打下基础,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 等高阶函数都已经分析完毕,用图总结:
image.png

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

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

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

持续更新中,和我一起步步为营系统、深入学习Android/Kotlin

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值