Kotlin中 run, let, with, also, apply和launch

410 阅读3分钟

一、概述

特征概括:
① 处理空的情况
② 提升程序的可读性
③ 大多数时候可以互相替代,所以不要死抠概念和区别

二、run

run是一块独立的执行区域,它会将最后一行内容回传带到下一个执行链条(chain)

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val name: String = "zhangSan"
        //因为最后一行代码为println没有返回值,所以run执行单元的返回值为Unit
        val result: Unit = run {   //属于MainActivity的run,不是上面name的执行单元
            val name: String = "liShi"
            println("name:$name")
        }
        
    }
}

打印结果: System.out: name:liShi

如果最后一行的返回值有扩展函数,那么就可以继续调用

val result:Int = run {
    10
}.plus(5)   //Int的扩展函数

println("打印结果:$result")

看一个复杂点的例子,偏实用,定义一个Person

data class Person(var name: String, var age: Int) {
    //定义一个打印的函数
    fun printInfo() {
        println("Person(name='$name', age=$age)")
    }
}

定义一个执行单元run

run {
  val person =  Person("zhangSan",18)
    person
 }.printInfo()  //Person的函数
 
打印结果: System.out: Person(name='zhangSan', age=18)

其他常用的就是空安全

val person = null
person?.run {
   ...
}

二、let

又或者可以写成 T.let,也是一个 extension function。T 在 scope 內则是用 it 来存取而不是 this。也可以依照需求改成其他的名字,增加可读性。与run相同,会将最后一行传到下一个链条或者回传。

例子同上面的run,此处略。

letrun的区别是如果想通过this访问外部内容时建议用letrun也可以只是let的可读性会更清晰

class MainActivity : AppCompatActivity() {

    var personOne = Person("zhangSan", 18)
    var bitmap: Bitmap? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val newName = "liSi"
        newName.run {
            personOne.name = this   //this指newName
            bitmap        //成员变量
        }

        //如果想通过this获取上层内容时建议用let,而不是用run
        newName.let { value ->   //如果不改为value,默认是it
            personOne.name = value    
            this.bitmap   //可以通过this访问外层,不加也可以,但是加了调用层次关系更清晰
        }


    }
}

三、with

with一般作为初始化使用,with(T)之中传入的值可以用this调用,不打出this也没关系。with也会将最后一行回传,目前来看基本都是用它做初始化,因为with括号内的内容可以很明确是在给谁进行初始化。

val textView = TextView(context)

with(textView){
    setTextColor(...)
    text = ""
    textSize = 12sp
}

如果要初始化的Tnull,那么就要加this?.

四、also

runwith把最后一行传递不同的是also传递的是自己

val options = ImageOptions().also {  //做配置
    it.res = res
    it.imageView = imageView
    it.context = context
    it.placeHolderResId = placeHolderResId
}

很多时候用also也有再带着干点啥的意思

Intent().run {
    putExtra(GlobalConstants.DATA_KEY, true)
}.also {
    setResult(RESULT_OK, this)
}

上面的例子是给Activity回传数据,这样的代码是不是就有kotlin代码的味道了?

五、apply

apply与 also 很像,不同的地方是 apply 在 scope 內 T的存取方式是 this ,其他都与 also 一样。常用于Builder建造者模式。看一个例子,比如需要一个图片配置的类,需要动态设置宽高,那么apply可以用于下面的代码:

//图片配置的类
object ImageOptions {

    class Builder constructor(context: Context) {
        private var width: Int = 0
        private var height: Int = 0

        fun setWidth(width: Int) = apply {
            this.width = width
        }

        fun setHeight(height: Int) = apply {
            this.height = height
        }
    }

}

当然apply一般场景也常用,举一个粗糙的例子

OkHttpClient.Builder().apply {
    //添加缓存拦截器
    addInterceptor(MyHeadInterceptor())
    // 日志拦截器
    addInterceptor(LogInterceptor())
    addInterceptor(TokenInterceptor())
    //超时时间 连接、读、写
    connectTimeout(10, TimeUnit.SECONDS)
    readTimeout(10, TimeUnit.SECONDS)
    writeTimeout(10, TimeUnit.SECONDS)
}

六、launch

launch其实属于协程的部分,跟上面的几个api放一起不合适,写在这里权当做记录吧

launch的源码

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

可以看到是协程的扩展函数,一般怎么用呢?举一个比较实用的例子。
① 获取ViewModel的协程

class SearchViewModel : BaseViewModel() {
    //与viewModel生命周期关联的协程
    val coroutineScope: CoroutineScope
        get() = viewModelScope   //需要导包
}

② 使用协程加Flow流

mViewModel.coroutineScope.launch {   //launch启动协程
    flow {   //流
        for (i in 0 until list.size) {
            val modelId = list[i].id
            if (modelId == recommendModelItemBean.id) {
                emit(i)   //发送拿到的结果
                break   //跳出循环
            }
        }
    }.collect { position ->   //拿到上游发送的结果
        //更新数据
        mAdapter.data[position].is_like = recommendModelItemBean.is_like
        mAdapter.notifyItemChanged(position)
    }
}

个人学习笔记

参考文章:

一文读懂 Kotlin 的 run, let, with, also 和 apply