Kotlin常用的run、let、apply、also、with等统称为作用域函数:
作用域函数 | 作用域对象 | 返回值 |
---|---|---|
run | this | 任意值 |
let | it | 任意值 |
apply | this | 调用者 |
also | it | 调用者 |
上表整理了各个函数的对比,还有一个with,可以理解为参数版本的run。
接下来具体介绍一下各函数的使用:
1.let
定义
public inline fun <T, R> T.let(f: (T) -> R): R = f(this)
使用场景
当receiver为nullable类型时,使用let配合?.可以简化判空操作;特别是当对某个nullable对象有连续操作时更加方便
val member : User?
...
//不使用?.
var name = ""
var age = ""
if (member != null) {
name = member.name.toUpperCase()
age = member.age.toUpperCase()
}
val id = name + age
//使用?.
val name = member?.name?.toUpperCase() ?: ""
val age = member?.name?.toUpperCase() ?: ""
val id = name + age
//使用let + ?.
val id = member?.let{
it.name.toUpperCase() + it.age.toUpperCase()
} ?: ""
2.run
定义
public inline fun <T, R> T.run(f: T.() -> R): R = f()
使用场景
run与let都用来完成一段计算你并返回任意值,区别仅在于block内是it(let)还是用this(run),那么如何区分两者的使用场景呢?
- 当receiver不是自身对象时(需要调用对象的公有成员或方法),使用let
- 当receiver是自身对象时(需要调用私有成员或方法),使用run
//run
class View {
fun doSth() {
...
val width = run {
if( !isShown ) 0
else childView.map{it.width}.sum()
}
...
}
}
//let
class Util {
fun doSth() {
...
val width = view.let {
if (!it.isShown) = 0
else it.childView.map{it.width}.sum()
}
...
}
}
3.apply
定义
public inline fun <T> T.apply(f: T.() -> Unit): T { f(); return this }
使用场景
apply常用用更新receiver自身属性
//不使用apply
class MyFragment: Fragment() {
companion object {
fun new(foo: Int, bar: Int): MyFragment {
val args = Bundle()
args.putInt("foo", foo)
args.putInt("bar", bar)
val myFragment = MyFragment()
myFragment.arguments = args
return myFragment
}
}
}
//使用apply
class MyFragment: Fragment() {
companion object {
fun new(foo: Int, bar: Int): MyFragment =
MyFragment().apply {
arguments = Bundle().apply {
putInt("foo", foo)
putInt("bar", bar)
}
}
}
}
4.also
also是1.1之后追加的作用域函数
定义
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
使用场景
also和apply的block对返回自身,区别在于it引用(also)还是this引用(apply)
- apply主要用于更新自身
- also常用于完成一个自身以外的副作用,不会更改返回值
class MyFragment: Fragment() {
companion object {
fun new(foo: Int, bar: Int): MyFragment =
MyFragment().apply {
arguments = Bundle().apply {
putInt("foo", foo)
putInt("bar", bar)
}
}.also {
Log.d(TAG, "${it.javaClass.name} created!")
}
}
}
5.with
定义
public inline fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()
使用场景
如前所属,with相当于参数版本的run,几乎可以由run替代。而且由于with不能配合?.使用,相对上面几个的使用频率较低,以我手头的一个Android项目为例,7000行代码中没有使用到with
函数 | 使用次数 |
---|---|
let | 54 |
with | 0 |
run | 40 |
apply | 37 |
also | 12 |
何为作用域?
作用域函数的作用域到底是什么意思呢?scope functions 一词最早来自于官方blog
What’s new in Standard Library M13 and M14
引用如下:
Prior to M13 there were two so-called scope functions in the Standard
Library: let and with. We call them scope functions, because their
only purpose is to modify scope of a function passed as the last
parameter.
作用域函数的主要目的是改变block的作用域,也就是改变block的receiver或者parameters。