Self Driven

兴趣是1,坚持是剩下的99

0%

讲讲 let,with,apply,also,run

新语言还是多练习的好,不然怎么会遇到问题呢。

遇到的问题

今天依然尝试用 kotlin 写写 demo,加载图片的时候写了下面这段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
fun loadImageLikeJava(imageUrl: String) {
Observable.fromCallable({
val `is` = JavaURL(imageUrl).openStream()
val bitmap = BitmapFactory.decodeStream(`is`)
val bitmapDrawable = BitmapDrawable(resources, bitmap)
bitmapDrawable.setBounds(0, 0, bitmap.width, bitmap.height)
bitmapDrawable
}).subscribeOn(io()).subscribe {
runOnUiThread({
tvTest.background = it
})
}
}

相比正常用 Java 写的逻辑,代码并没有少多少,只是类型推断跟 lambda 让代码看起来干净不少。可是如果这么用 kotlin 并没有多少好,“能不能让代码再少点”,想了一会就改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
fun loadImage(imageUrl: String) {
Observable.fromCallable {
BitmapFactory.decodeStream(JavaURL(imageUrl).openStream())
.let { bitmap -> BitmapDrawable(resources, bitmap)
.also { it.setBounds(0, 0, bitmap.width, bitmap.height) }}
}.subscribeOn(Schedulers.io()).subscribe {
runOnUiThread {
tvTest.background = it
}
}
}

用上了 letalso 之后,Callback 里就只剩下一句。
Kotlin 标准库里的这几个内置函数特别好用,但跟其他几个函数一起的话经常容易让人搞混,于是在这里一起总结一下以做记录。

关于 let, also, run, with, apply

先看几个函数的签名:

1
2
3
4
5
6
7
8
9
10
11
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

public inline fun <R> run(block: () -> R): R = block()

public inline fun <T, R> T.run(block: T.() -> R): R = block()

public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  • T.apply()T.also() 是类似的,他们都可以像 Builder 模式一样链式调用。但 T.apply() 的参数是 T.() -> Unit, 即要执行的 block 就像是在 T 内部一样,如果需要调用 T 的方法不需要 it 指明,而 T.also() 就需要。
  • T.let()T.run() 也是类似的,执行函数后返回一个值,就像是 L map R 一样,但 T.run()T.apply() 类似,block 都像是在 T 内部一样。
  • with()run() 跟上面四个函数都不大一样,这两个函数不需要在 T 上调用。
    • run() 是 block 返回一个值 R,跟 Callable 有点像,但直接 new Callable() 没法直接返回一个值,只能是一个 Callable
    • with() 则是这里面唯一一个接受两个参数的(虽然经过 lambda 表达式的简化之后看起来可以像是一个),第一个参数是接受调用的参数,第二个参数是作用在第一个参数上的方法。这跟 T.apply() 非常像,但 T.apply() 返回的是自身,with() 执行结束后可以返回其他值。
  • 简单点记的话是不是可以这样:`also let it go, apply run like inside`

let 一个常见用法

今天在调用一个 nullable 的对象的时候出现了 kotlin 的 smartcast 「失效」 的情况:

1
2
3
4
5
6
7
8
9
10
11
12
var loadDrawable: Drawable = null

fun loadFinish() {
loadDrawable = BitmapDrawable(resources, Bitmap.createBitmap(null))
if (loadDrawable != null) {
doSomething(loadDrawable)
}
}

fun doSomething(drawable: Drawable) {
// DO SOMETHING
}

我本以为经过 != null 的判断,loadDrawable 已经不可能会空,但 IDE 是这么提示的:「无法智能地将 BitmapDrawable? 转换为 BitmapDrawable,因为 loadDrawable 是可变属性,在调用的时候有可能被改变」。仔细想想确实也是,虽然我这里没有没有操作,但一旦有多线程操作,loadDrawable 有可能为 null。如果真的确定不会为 null 或者只会在同一个线程内修改,强制用 loadDrawable!! 是可以的,但有没有更优雅点的方式呢?
有的,就是用 let(also 也可以,但这里并不需要返回 this)。

1
2
3
4
fun loadFinish() {
loadDrawable = BitmapDrawable(resources, Bitmap.createBitmap(null))
loadDrawable?.let { doSomething(it) }
}

这样编译器就不会再提醒 smart cast 失败了。
参考 let vs if not null