談談Kotlin:Kotlin每一行程式碼都有返回值

  • 2019 年 10 月 6 日
  • 筆記

?:+let 實現的 if-else

這周在網上衝浪的時候,看到了這麼一個討論:「Elvis運算符與return組合的語句,在return前增加邏輯,如何寫得優雅?」,裡面提到一個「使用let語法糖結合?:運算符實現if-else」的示例:

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 語法糖

結合實現,可以看到,let會在block執行完後,返回block的返回值。

而Kotlin和Java不同,在Kotlin里每一行程式碼都是表達式,也就是說每一行程式碼執行完畢後都有一個返回值。

接下來考慮如下例子:

// 例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")  }

會得到如下輸出:

[nullVal] null code block    [notnull] not null code block  [notnull] null code block

例2的輸出顯然是不符合預期的。

在文章一開始的那個例子里,由於it.name = "Hello"的返回值是Unit,是一個非空的值,因此能夠如預期,呈現出和if-else等價的效果,但這裡實際上會留下一個隱藏的坑。

寫程式碼的時候,肯定不會寫出我上面舉的例子那麼傻的程式碼,考慮如下變種:

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)的寫法其實是最可愛的。

Tips

介紹一個小技巧:

IDEA編輯器提供了快速判空的模板,在變數後輸入.nn回車。

就能收穫如下程式碼。

PS:nnnotnull的縮寫,輸入.notnull也有同樣的功效。