欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Kotlin内置函数之 let 、also、apply、run、with

程序员文章站 2022-05-29 13:36:35
...

前言

在Kotlin中的源码标准库(Standard.kt)中提供了一些Kotlin扩展的内置函数可以优化kotlin的编码。Standard.kt是Kotlin库的一部分,它定义了一些基本inline函数。这些内置函数可以优化kotlin的编码,更优雅,更有效率的实现我们的功能。

内联函数之with

我们先来看下这个函数实现

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#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和一个lambda函数块,返回值结果是函数执行的结果。但它不是以扩展的形式存在的,它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象,this可以省略。

使用姿势

var people = People("zg",20,165)
val result = with(people, {
   println("name is $name, age is $age , height is $height")
   1000
})
//或者 由于with函数最后一个参数是一个函数,可以把函数提到圆括号的外部
val result = with(people) {
   println("name is $name, age is $age , height is $height")
   1000
}
println(result)

打印结果:
name is zg, age is 20, height is 165
1000

应用场景

with适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可。经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上。

override fun onBindViewHolder(holder: ViewHolder, position: Int){
   val item = getItem(position)?: return
   with(item){
      holder.name.text = name
	  holder.age.text = age
	  holder.height.text = height
      ...   
   }
}

内联扩展函数之 let 和 also

我们先来看下这两个函数的实现

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {this ContracBuilder)
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {this ContracBuilder) 
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

看到这两个内置函数的实现,你大概知道为什么我要把它们俩放一起了。可以看到这两个inline函数的注释和实现可以说几乎是相同的。唯一不同的是let函数returns its result(返回函数体的返回),而also函数则是returns this value(返回调用函数者)。

使用姿势

首先来看let,在函数体内用it代替调用者

var result = "test".let {
    println(it.toString())
    println(it.indexOf("s"))
    1000  //最后一行结果返回
}
println(result)

打印结果:
test
2
1000

接下来看also,在函数体内用it代替调用者

//返回结果为调用者
var result = "test".also {
	println(it.toString())
    println(it.indexOf("s"))
    1000
}
println(result)

打印结果:
test
2
test

可以看到两者除了返回结果之外,其他的都是差不多的。那么这两者应用场景分别是什么了?

应用场景

let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

场景一: 最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。

obj?.setXXX1()
obj?.setXXX2()
obj?.setXXX3()
...

使用let函数:
obj?.let {
	it.setXXX1()
	it.setXXX2()
	it.setXXX3()
	...
}

场景二: 明确一个变量所处特定的作用域范围内可以使用。

also适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象,一般可用于多个扩展函数链式调用。

var people = People("zg",20,165)
println(people)
//链式调用
var result = people.also {
   it.name = "zg1"
}.also {
   it.height = 175
}.also {
   it.age = 25
}
println(result)

打印结果:
People(height=165, name=zg, age=20)
People(height=175, name=zg1, age=25)

内联扩展函数之apply和 run

我们先来看下这两个函数的实现

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#apply).
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

通过查看注释和源码实现发现其实这两个函数也有很多共同点。apply返回的是调用者,而run返回的是闭包的最后一行。同时两者在函数体中都用this来代替调用者,this并且可以省略。

使用姿势

首先看apply,在函数体内用this替代调用者,this可省略。

var result = "test".apply {
   println(toString()/*this.toString()*/)
   1000
}
println(result)//返回调用者

打印结果:
test
test

接下来看run,在函数体内用this替代调用者,this可省略。

var result = "test".run{
   println(toString()/*this.toString()*/)
   1000
}
println(result)//返回函数体闭包中的最后一行

打印结果:
test
1000

同样,出了返回结果又差异之外,其他差不多。

使用场景

apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。

var people = People().apply {
	name = "xxx"
	age = 20
	height = 175
	...
}

run适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理。

let,with,run,apply,also函数区别

通过以上几种函数的介绍,可以很方便优化kotlin中代码编写,整体看起来几个函数的作用很相似,但是各自又存在着不同。使用的场景有相同的地方比如run函数就是let和with的结合体。下面一张表格可以清晰对比出他们的不同之处。
Kotlin内置函数之 let 、also、apply、run、with

尾巴

今天的笔记就这么多了,老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!

上一篇: Android自定义控件

下一篇: 第二天