持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
前言
在Kotlin的源码 Standard.kt 中提供了一些 Kotlin 扩展的内置函数可以优化kotlin的编码。
我们常用的几个函数 also apply let run with 都是来自与 Standard 类,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个 lambda 表达式时,它会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称。这些函数称为作用域函数。
那他们有什么区别呢?使用的时候又该如何使用?
一、run 函数
源码:
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
一种是直接用的,另一种是扩展方法,对象调用的。
其实一个参数是高阶函数,一个是扩展方式的高阶函数,我在之前的文章已经讲过很多次了,其本质就是高阶函数。
他们唯一的区别就是this和it的区别,this不可以用自定义变量来代替,而it可以使用自定义变量名来接收。
这里 callsInPlace 方法是一个校验方法,这个函数有两个作用,一是确认当前函数调用的位置是否恰当,二是确认当前函数调用的次数。
run的使用的两种方式:
非扩展的使用:
val num = 123
val num1 = kotlin.run {
YYLogUtils.w("num :" + num)
return@run num + 1
}
YYLogUtils.w("num1 :" + num1)
这样是非扩展的方式,可以定义一个作用域,同时我们可以return一个对象出去。
只定义代码边界
run outside@{
mViewModel.mAllDeployments?.forEach {
//替换掉休息时间
if (it.id == deployment_id) {
it.break_time = time
return@outside
}
}
}
比如我们可以在遍历中,协程中或者其他的一些任务中很方便的返回一个对象,因为他是一个作用域函数,run函数创建了一个作用域,我们直接指定返回到这个作用域,这个循环就会终止。
扩展方式的run方法使用
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
因为扩展方式的run函数,它的参数是高阶扩展函数,所以我们可以直接使用this来设置属性。
二、with 函数
说完run函数之后,我们再看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()
}
它和run函数的区别:函数的参数不同,需要传入对象,由于第二个参数也是高阶扩展函数,所以其他的使用都是相同的
val num5 = with(num) {
toString().trim()
return@with 111
}
YYLogUtils.w("num5 :$num5")
打印结果:
三、let 函数
有点不同了哦,它和run的扩展方法相比,一个参数是高阶扩展函数,一个是高阶函数。
block: (T) -> R 和 block: T:() -> R 的区别而已,本质还是一样的。
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
由于是高阶函数作为参数,所以我们使用it来操作。
val num4 = num.let {
it.toString().trim()
return@let "222"
}
YYLogUtils.w("num4 :$num4")
Log如下
run with let的区别
with(webview.settings) {
javaScriptEnabled = true
databaseEnabled = true
}
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
let 和 run 其实差不多,都是返回最后一行或者指定return
let和run的返回值相同,它们的区别主要在于作用域内使用it和this的区别。 一般来说,如果调用者的属性和类中的属性同名,则一般会使用let,避免出现同名的赋值引起混乱。
四、apply 函数
又有一点不同了,如同把前三者做一个比较一样,后两者之间常常用作一个比较,他们都是返回this,也就是对象本身。
只是他们区分了this和it,也就是高阶扩展函数当参数还是高阶函数当参数的问题。
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
由于使用的是高阶扩展函数,所以使用的时候要用this。
num.apply {
toString().trim()
}
由于返回的this,使用的时候压根就不能return,如果强制return也会直接报错。
五、also 函数
also是与apply想对应的函数,它的参数是高阶函数,所以我们使用的私有使用it接收,当然我们也可以指定参数接收
num.also { value ->
value.toString().trim()
}
同样的也不能手动的指定返回,因为返回的是对象本身。
从本质上来说他们都差不多,都可以用,但是我们根据函数的名称,和一些使用习惯,它们在使用的时候,还是有一些细微的差别的, also强调的是【与调用者无关的操作】,而apply强调的是【调用者的相关操作】,例如下面的这个例子。
test?.also {
//跟自己无关的逻辑
println("some log")
}?.apply {
//修改自己内部的属性
name = "xys"
}
总结
用一个图片来总结他们就是:
其实我们常用的就是run与let apply与also。他们两两相对,主要是以是否返回自身作为区别。
run与let返回的是最后一行或者手动return的数据,而apply与also他们返回的是自身,且不支持自定义return,会编辑器报错。
用一个示例来说明就是:
//new一个animset,传入的是对象本身,apply用run好像也可以
AnimatorSet().apply {
ObjectAnimator.ofPropertyValuesHolder(
textView,
PropertyValuesHolder.ofFloat("scaleX", 1.0f, 1.3f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f, 1.3f)
).apply {
duration = 300L
interpolator = LinearInterpolator()
}.let {
play(it).with(
ObjectAnimator.ofPropertyValuesHolder(
button,
PropertyValuesHolder.ofFloat("translationX", 0f, 100f)
).apply {
duration = 300L
interpolator = LinearInterpolator()
}
)
play(it).before(
ValueAnimator.ofInt(ivRight,screenWidth).apply {
addUpdateListener { animation -> imageView.right= animation.animatedValue as Int }
duration = 400L
interpolator = LinearInterpolator()
}
)
}
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {}
override fun onAnimationEnd(animation: Animator?) {
Toast.makeText(activity,"animation end",Toast.LENGTH_SHORT).show()
}
override fun onAnimationCancel(animation: Animator?) {}
override fun onAnimationStart(animation: Animator?) {}
})
start()
}
上述代码是构架一个动画,将动画1和动画2一起播放,将动画3在动画1之后播放。
其实源码非常的简单,我们有一次复习了高阶函数参数与高阶扩展函数参数的区别,关于这一点可以看看我之前的文章。
好了,本文的全部代码与Demo都已经开源。有兴趣可以看这里。项目会持续更新,大家可以关注一下。
如有讲解不到位或错漏的地方,希望同学们也可以指出交流。
如果感觉本文对你有一点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。