1. 有关作用域函数:
Kotlin
中有很多作用域函数, 但是常见的使用场景是不同的。
常见的五种作用域函数 let
, run
also
, apply
, with
, 所有的对象均可使用这些函数。
四种作用域函数,最直观的区别是:
-
let
,run
会直接返回闭包的返回结果;源码如下:
// let 函数, 返回结果是 block, 且 T 作为闭包参数 「`(T)`」 public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) } // run 函数如下, 闭包没有参数 public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
同时从代码中,可以看到
let
中,T
是作为block
的一个参数「(T)
」,
而run
则没有闭包参数 「T.()
」
-
also
,apply
闭包返回的结果是调用者本身源码实现:
// alse 函数, 返回结果是 this, 且 T 作为闭包参数 「`(T)`」 public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this } // apply 函数, 没有闭包参数 public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
因为
also
,apply
的 返回值为当前调用者本身, 所以比较方面进行链式调用,和组合使用。test.also{ println("first also") }.also { println("second also") }.apply { name = "hahaha" }.also { println("third also") }
注: 在上面的
apply
和run
中,闭包的形式为T.()
也就是说,在调用时,在{}
中是可以直接访问T
里面的方法和变量的updateView()
,而不需要显示的写为T.updateView()
-
let
,run
also
,apply
,with
在闭包中的引用符号差别it
和this
从上面的源码中可以发现:
let
,also
它的调用者是闭包的参数,可以用it
引用该调用者:var test : Test? = null test?.let { println(it.name) }
而
run
和apply
闭包是无参的忙, 可以用this
来引用该调用者:var test : Test? = null test?.run { // 该 name 为 Test 对象的属性 println(name) // 有时为来区分 name 和这个 闭包外面的成员变量 name, 为了代码可读性, 也可使用下面的方式 println(this@run.name) }
场景使用
1 also
和 apply
的调用场景:
两个都默认返回调用者本身。
1.1 also
更注重的是:Additional effects
额外效果
即:一个对象在调用中,做的其他的额外处理。
test?.also {
println("复值前$it.name")
}?.apply {
name = "hahaha"
}?.also {
println("复值后")
}
当要处理的逻辑不涉及调用者 T
本身时,使用 also
.
1.2 当使用场景是 Object configuration
对象参数配置, 使用 apply
当是对一个对象进行参数配置时「可连续配置,链式调用」,可以使用该方式:
return AnswerDividerItemDecoration2.instance(context).apply {
dividerHeight = 10.dp
setPaintColor(R.color.xxx)
setPaintLightColor(R.color.xxx)
}
以上代码是返回结果是一个 AnswerDividerItemDecoration2
对象 instance
。
AnswerDividerItemDecoration2.instance(context)
返回的对象就是 instance
, 我们利用 apply
对其进行了配置,并且闭包返回调用者本身 instance
, 再利用 return
返回 。
2. let
和 run
的调用场景:
两者都返回闭包最后的执行结果。
- 当不需要返回调用者
T
本身时,可以使用两者; - 当对闭包的返回结果不关心时,可以使用两者;「个人更喜欢用
run
」 - 当需要对闭包最后的执行结果操作时,可以使用两者;「个人更喜欢用
run
」
2.1 let
的使用偏向:
当使用 run
时调用者的一些属性和外部的属性重合时可以使用 let
的 it
例如:
// it.name 指的是 test 对象里面的属性 name;
test?.let {
it.name = name // 这个 name 指的是当前方法外部的成员变量 name
it.count = count
}
2.2 run
的使用偏向:
当涉及到很多复杂的逻辑时需要使用该方式 run
例如代码:
fun test4() {
val beanList = mutableListOf<BeanTest>()
beanList.run {
println("beanList 转化数据为 bean1List")
toBean1List(this)
}.run {
println("bean1List 转化数据为 bean2List")
toBean2List(this)
}
}
上述代码, 通过 run
, 把 toBean1List()
的返回值 bean1List
作为调用者再次调用了 run
.
这也是 run
的优势,可以非常便捷的执行复杂的逻辑操作,数据转换等工作。
2.3 当仅仅用作判空处理时:不推荐使用 let
代码示例如下:
fun test2(str:String?) {
if (str != null) {
// do something,与 str 变量无关
println("hahaha")
}
str?.let {
// do something,与 str 变量无关
println("hahaha ---> let")
}
}
上面两种方式,看起来差别不大,但是当 kotlin
被编译成 java
代码时:
public final void test2(@Nullable String str) {
if (str != null) {
String var2 = "hahaha";
System.out.println(var2);
}
if (str != null) {
int var4 = false;
String var5 = "hahaha ---> let";
System.out.println(var5);
}
}
会发现,使用 let
的里面多了一行 int var4 = false;
引入了一个新的变量,这在该方法被多次调用时,是会占用内存的~
所以,当仅仅是用作判断是否为 null 时,可简单的使用 if()
去判断。
2.4 当需要对调用者 T
对象里面的参数进行赋值时,且不涉及到外部的内容,使用 run
例如:
// 不推荐这么使用
webviewSetting?.let {
it.javaScriptEnabled = true
it.databaseEnabled = true
}
// 推荐使用 run
webviewSetting?.run {
javaScriptEnabled = true
databaseEnabled = true
}
上面两种方式,本质上没有差别,但是第二种方式消除了 it
的使用, 代码上更舒服。
2.5 当为了去除调用者多个 ?. 判断时,可以使用 run
例如代码:
fun test3(str: String?) {
// 第一种方式, 需要每个都需要判断是否空
str?.asIterable()?.iterator()?.next()
// 第二种方式, 使用 `!!` 强制断言不为 null, 这样可以省去 ? 判断
str!!.asIterable().iterator().next()
// 第三种方式,推荐使用
str?.run {
asIterable().iterator().next()
}
// 第四种方式
if (str != null) {
str.asIterable()
}
}
上面代码中,一共有三种方式,
- 第一种是不友好的方式,有很多冗余的判断;
- 第二种方式,也不太好,因为使用
!!
如果为空,直接crash
, 慎用!!
- 第三种方法推荐使用,更简洁;
- 第四种方法 ide 检测会报红的,因为 if(str != null) 里面 在
kotlin
并不能自动把str
转变为非null
的
参考链接:
网友评论