Kotlin 基础:从 null safety 到 Standard.kt
这篇文章依旧没有什么主题、没有什么深度,纯碎随笔,谈的也是 Kotlin 最最基础的问题。
我常常说 Kotlin 比 Swift 舒服的一点是,Kotlin 在 null-safety 问题上,会有所谓「smart cast」。例如你有
var a: Foo? = getFoo()
if (a != null) {
print(a.foo) // 不再需要 !!
}
而在 Swift 里,你仍然需要手动再次「!」
var a: Foo? = getFoo()
if (a != null) {
print(a!.foo) // 仍然需要 !
}
当然所有人都会很快注意到的一点是,Kotlin 对于局部变量可以做这样的处理,但如果是个类的成员变量(var
),就不行:
class Bar {
var a: Foo? = getFoo()
fun bar() {
if (a != null) {
print(a.foo) // 编译无法通过
}
}
}
为什么 Kotlin 此时不予编译通过呢?因为在当前线程判断 a != null
之后,其它线程可能又修改了「a」的值。事实上,如果是用 val
声明的 field,此时就可以 smart cast。
而如果是 var
声明的呢?如果你加上 !!
,变成 a!!.foo
,就可以编译通过,但这么做的话,你就会被抓住游街。所以,正确的解决方式是,定义一个局部变量,这样就确保没有其它线程能动到它。
fun bar() {
val localA = a
if (localA != null) {
print(localA.foo) // cool
}
}
这样写的唯一问题,就是丑。定义了一个可能只用到一次的局部变量,而它并不被整个方法所需要,而仅仅只是临时用到而已。大约 Swift 程序员们也意识到了这一点,所以 Swift 里习惯的 practice 是:(摘自官方教程)
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
也就是,为了写得更简明一些,Swift 从语法层面增加了 if let
语句解决问题。
Kotlin 里,当然也可以利用 let
来写得漂亮些。当然,Kotlin 的 DSL 能力那么好,何必再从语法层面解决问题?
print(john.residence?.numberOfRooms?.let { "John's residence has $it room(s)." } ?: "Unable to retrieve the number of rooms.")
一气呵成,不需要任何额外的语法。事实上,我实际写代码时,也经常会 foo?.let { … }
这么用。
上面语句的核心就是 let
。和 Swift 不同,Kotlin 里的 let
不过只是一个普通的函数而已。事实上,很多 Kotlin 教程都不会提及标准库里这几个很赞的函数。它们实现得非常简单,但却可以作为良好 Kotlin 代码的典范:
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 }
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
熟悉它们,能够帮你大大地缩短代码。
例如某个方法,在计算出返回值后,还需要做一些清理,之前你可能:
fun foo(): Int {
val resource = getResource()
val retVal = resource.calculate()
resource.cleanUp()
return retVal
}
而现在,仅靠标准库里的这几个方法,你就可以:
fun foo() = getResource().run { calculate().also { cleanUp() } }
标准库里的这几个方法仅仅是最最常用和最简单的对控制语句的补充。其实还有很多这样的扩展方法可以用,例如 funKTionale 里面的函数,都很棒呢。