Kotlin深入浅出系列---作用域函数-let、run、with、apply和also

369 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第一天,点击查看活动详情

Kotlin深入浅出系列---let、run、apply、with、also

前言

在我们学习Kotlin语法中,会看到以下五种函数:letrunwithapplyalso。这五种函数在Kotlin中叫做作用域函数,是Kotlin标准库中的函数,它们的唯一目的是在对象的上下文中执行代码块

  • 作用域函数 :当一个对象调用这个样的函数并提供一个lambda表达式时,他会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称

简单来说:可以减少冗余代码,从而让代码更简洁;可以让代码形成链式调用,从而使代码逻辑更清晰。

作用域函数

我们在上面已经了解到了letrunwithapplyalso都是在在做一件事情,在对象的上下文中执行代码块

使用作用函数

val paint = Paint()

paint.let { 
    //设置画笔颜色
    it.color = Color.RED
    //设置填充样式--填充
    it.style =Paint.Style.FILL
    //设置画笔宽度
    it.strokeWidth =50F
}

不使用作用函数

val paint = Paint()
//设置画笔颜色
paint.color = Color.RED
//设置填充样式--填充
paint.style =Paint.Style.FILL
//设置画笔宽度
paint.strokeWidth =50F

通过上面的代码,我们看出在不引用任何新技术的情况下,我们使用作用函数使代码更加的简洁、易读

let、run、with、apply和also具体使用与区别

我们知道let、run、with、apply和also都是作用函数,我们来看一下它们的具体使用和区别

let

调用某对象的let函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为函数块的最后一行或指定return表达式

源码:

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

使用:

val paint = Paint()

val letReturn = paint.let {
    it.color = Color.RED
    //设置填充样式--填充
    it.style =Paint.Style.FILL
    //设置画笔宽度
    it.strokeWidth =50F
}

let会返回最后一行代码,这里返回的是Unit,所以letReturn是Unit

val letReturn = paint.let {
    it.color = Color.RED
    //设置填充样式--填充
    it.style =Paint.Style.FILL
    //设置画笔宽度
    it.strokeWidth =50F
    1
}

这里letReturn就是1啦

run

调用某对象的run函数,在函数块内可以通过 this 指代该对象。返回值为函数块的最后一行或指定return表达式

源码:

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

使用:

val paint = Paint()

val runReturn = paint.run {
    this.color = Color.RED
    //设置填充样式--填充
    this.style =Paint.Style.FILL
    //设置画笔宽度
    this.strokeWidth =50F
}

这里it变成了this,返回值也会是this(调用run函数自身,这里就是paint)
runReturn就是paint

with

with函数和前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。返回值和let一样,为函数块的最后一行或指定return

源码:

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

使用:

val paint = Paint()

val withReturn = with(paint) {
    this.color = Color.RED
    //设置填充样式--填充
    this.style =Paint.Style.FILL
    //设置画笔宽度
    this.strokeWidth =50F
}

这里和let返回值是一样的,不做过多解释,详细请看let

apply

用某对象的apply函数,在函数块内可以通过 this 指代该对象。返回值为该对象自己

源码:

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

使用:

val paint = Paint()

val applyReturn = paint.apply {
    this.color = Color.RED
    //设置填充样式--填充
    this.style =Paint.Style.FILL
    //设置画笔宽度
    this.strokeWidth =50F
}

返回值是调用者本身,所以applyReturn为paint

also

调用某对象的also函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己。

源码

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

使用:

val paint = Paint()

val alsoReturn = paint.also {
    it.color = Color.RED
    //设置填充样式--填充
    it.style =Paint.Style.FILL
    //设置画笔宽度
    it.strokeWidth =50F
}

返回值为调用者本身,所以alsoReturn为paint

总结

函数函数块内使用对象返回值是否为扩展函数使用场景
letit函数块最后一行或return表达式的值适用于对象统一处理不为空的情况
runthis函数块最后一行或return表达式的值适用with()、let()函数的任何场景
applythis该对象本身适用于run()函数的任何场景,通常可用来在初始化一个对象实例时,操作对象属性并最终返回该对象。也可用于多个扩展函数链式调用
withthis函数块最后一行或return表达式的值否,对象作为参数适用于调用同一个类多个方法
alsoit该对象本身适用于let()函数的任何场景,一般可用于多个扩展函数链式调用

上下文对象:this还是it

我们在上文中了解到,我们的作用域函数中,可以使用it或者this来访问上下文对象。

  • lambda 表达式的参数(it) : let、also
  • lambda表达式的接收者(this): run、with、apply

lambda表达式的接收者 this

run、with和apply通过this关键字来进行对上下文的访问,因此,在其lambda表达式中我们可以在类函数中一样访问上下文。当然我们也可以省略this,当我们要对外部对象的成员进行修改时,我们可以使用This 表达式来表达该成员变量为外部类的

val paint = Paint()

paint.apply {
    this.color = Color.RED
    //设置填充样式--填充
    this.style =Paint.Style.FILL
    //设置画笔宽度
    this.strokeWidth =50F
    
    也可以省略this、效果是一样的
    
    color = Color.RED
    style =Paint.Style.FILL
    strokeWidth =50F
}

//
class Test :View(){
    var color  = Color.RED
    
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        val paint = Paint()

        paint.apply {
        //使用this语法来调用外部类的成员变量
        this@Test.color = Color.RED
        }
    }

lambda 表达式的参数 it

it和also是将上下文对象作为lambda表达式参数来进行对上下文的访问。如果没有指定参数名,默认为it,it比this更易读,因此,当上下文对象在作用域中,主要用作对函数的调用中的参数时,使用it作为上下文对象会更好

val paint = Paint()

paint.let { //这里不写参数名默认为it  也可以指定,如:value ->来进行修改默认参数名
    //设置画笔颜色
    it.color = Color.RED
    //设置填充样式--填充
    it.style =Paint.Style.FILL
    //设置画笔宽度
    it.strokeWidth =50F
}