1. 程式人生 > >Kotlin如何避免“!!”(非空斷言)

Kotlin如何避免“!!”(非空斷言)

當我們把Java自動轉成Kotlin的時候,程式碼裡會出現很多非空斷言!!。或者某些場景下因為IDE提示或編譯錯誤,也讓我們自己加上了一些!!
但使用!!的後果是有可能丟擲IllegalArgumentException:Parameter specified as non-null is null

如何避免!!

使用?.let/?.apply/?.run

這種是最常用的方法,也是首選的方法。但當有多個變數同時要判空時,或者需要處理為null時的邏輯,這種方式稍微有一點麻煩,下面會講到一些新的方式。

disposable?.let {
    if (!it.isDisposed)
it.dispose() }

用Val替代Var

var mutableString:String? = null

fun run() {
    mutableString = "a"
    printText(mutableString)
}

fun printText(text: String) {
    ...
}

此時會報錯Smart cast to 'String' is impossible, because 'multableString' is a mutable property that could have been changed by this time :app:compileDebugKotlin FAILED

。由於multableString是Var變數,為了避免多執行緒對變數的修改而出現Null的情況,kotlin從編譯上進行了限制。

  • 解決方法1是把var變數改為val變數
val mutableString:String = "a"

fun run() {
    printText(mutableString)
}
  • 解決方法2是寫一個新的val變數,將var變數賦值給它,將val作為引數
fun run() {
    mutableString = "a"
    val string = mutableString ?: ""
    printText
(string) }

使用Elvis操作符

fun run() {
    multableString = "a"
    printText(multableString ?: "")
}

宣告lateinit

使用lateinit宣告到變數上,表示這個變數延遲初始化,比較適合在Activity.onCreate這種有生命週期的方法裡初始化。

lateinit var mutableString: String
override fun onCreate(savedInstanceState: Bundle?) {
    multableString = "a"
    printText(mutableString)
}

需要注意的是,訪問未初始化的 lateinit 修飾的屬性會丟擲UninitializedPropertyAccessException異常

注意:基本型別是不能使用lateinit的。會拋錯'lateinit' modifier is not allowed on properties of primitive types

lateinit var mutableInt: Int

代理屬性

如果需要對基本型別等做非空處理,可以使用代理屬性。

var mutableInt: Int by Delegates.notNull<Int>()
override fun onCreate(savedInstanceState: Bundle?) {
    mutableInt = 1
}

一定要在初始化賦值之後才能讀取mutableInt,不然會拋IllegalStateException:Property ${property.name} should be initialized before get.

空與非空處理

val result = multableString.notNullElse {
    "$it is not null"
} ({ "is null" })

新開發的方法notNullElse,對單個變數判空處理,非空時傳入it為非空型別,提高了便捷性,為空時使用第二個block來返回值。適合那裡需要判空,返回值result也是非空的型別,比較實用。原始碼在此下載

多個值非空

private var mLinearLayout: LinearLayout? = null

...
private fun initView(context: Context) {
    mLinearLayout = LinearLayout(context)
}

...

if (tvItem == null) {
    mLinearLayout!!.addView(childTvItem)
} else {
    mLinearLayout!!.addView(childTvItem, mLinearLayout!!.indexOfChild(tvItem) + 1)
}

當我們要對多個值判斷的時候,let就不那麼好用了,但如果不使用let就拿不到非空的型別,像上面要判斷2個都不為空時做操作,為空時另外一個邏輯。其實一早我們就已經判斷空了,有沒有更好的方法呢?

allNotNullElse(tvItem, mLinearLayout) { a, b ->
    b.addView(childTvItem, b.indexOfChild(tvItem) + 1)
} ({ mLinearLayout?.addView(childTvItem) })

新開發的方法allNotNullElse返回的a, b 兩個值已經是非空型別了,這樣addView使用的也是非空型別,使用起來更方便了。原始碼在此下載