?:
+let
實現的 if-else
?這周在網上衝浪的時候,看到了這麼一T個討論:「Elvis運算符與return組合的語句,在return前增長邏輯,如何寫得優雅?」,裏面提到一個「使用let
語法糖結合?:
運算符實現if-else
」的示例:bash
account?.let {
it.hello()
it.name = "Hello"
} ?: run {
logger.error("account is null")
}
複製代碼
乍一看,這種寫法很新穎頗有創意,但實際上let
語法糖後接?:
這種作法是有問題的。編輯器
看下let
語法糖的函數聲明:public inline fun <T, R> T.let(block: (T) -> R): R
函數
結合實現,能夠看到,let
會在block
執行完後,返回block的返回值。spa
而Kotlin和Java不一樣,在Kotlin裏每一行代碼都是表達式,也就是說每一行代碼執行完畢後都有一個返回值。code
接下來考慮以下例子:cdn
// 例1:可空變量爲空
val nullVal: Any? = null
nullVal?.let {
println("[nullVal] not null code block")
null
} ?: run {
println("[nullVal] null code block")
}
// 例2:可空變量爲非空
val notnull: Any? = Any()
notnull?.let {
println("[notnull] not null code block")
null
} ?: run {
println("[notnull] null code block")
}
複製代碼
會獲得以下輸出:blog
[nullVal] null code block
[notnull] not null code block
[notnull] null code block
複製代碼
例2的輸出顯然是不符合預期的。ip
在文章一開始的那個例子裏,因爲it.name = "Hello"
的返回值是Unit
,是一個非空的值,所以可以如預期,呈現出和if-else
等價的效果,但這裏實際上會留下一個隱藏的坑。string
寫代碼的時候,確定不會寫出我上面舉的例子那麼傻的代碼,考慮以下變種:it
fun test_let() {
val nullable: Any? = null
nullable?.let {
println("[nullable] not null code block")
maybeReturnNull(0)
} ?: run {
println("[nullable] null code block")
}
val notnull: Any? = Any()
notnull?.let {
println("[notnull] not null code block")
maybeReturnNull(0)
} ?: run {
println("[notnull] null code block")
}
}
private fun maybeReturnNull(count: Int): Any? = if (count % 2 == 0) null else Any()
複製代碼
一旦命中這樣的坑,查起來挺費勁的 QAQ
掉過這樣的坑後,就會發現樸素的if (xxx != null)
的寫法實際上是最可愛的。
介紹一個小技巧:
IDEA編輯器提供了快速判空的模板,在變量後輸入.nn
回車。
就能收穫以下代碼。
PS:nn
是notnull
的縮寫,輸入.notnull
也有一樣的功效。