常見Kotlin高頻問題解惑

文 | 歐陽鋒

在筆者的Kotlin交流群里,不少同學(xué)反復(fù)遇到了一些相似的問題。這些問題大都比較基礎(chǔ),但又容易產(chǎn)生誤解。因此,我決定寫一篇文章,整理群里同學(xué)遇到的一些問題

變量和常量的使用

在Kotlin語言中,我們使用var聲明變量,使用val聲明常量。由于來自Java語言中沒有區(qū)分常量變量的影響,一些同學(xué)對這兩個關(guān)鍵字的理解有問題。為了理解這兩個變量的區(qū)別,我們可以用兩個等式來說明一下:

var str: String = "abc"  => public String str = "abc"

val str: String = "abc" => public final String str = "abc"

=>符號后面是對應(yīng)的Java代碼,Java語言使用final關(guān)鍵字聲明常量。很明顯,使用明確的變量和常量聲明更有助于理解。

注:一些Java程序員很少使用final關(guān)鍵字,這說明這部分同學(xué)對于常量的使用不太理解。事實(shí)上,JVM中有一個常量池,如果發(fā)現(xiàn)常量池中存在該值就直接使用;反之,則創(chuàng)建并存入常量池。從這個層面來說,使用常量比使用變量效率更高。更重要的是,如果你聲明一個不會被改動的變量,使用final修飾將更準(zhǔn)確,也更安全。

lateinit

其實(shí),在使用Kotlin語言的這兩年里,我從來沒有用過這個關(guān)鍵詞。但剛剛接觸Kotlin語言的同學(xué)似乎很喜歡使用這個修飾符修飾變量。

這個關(guān)鍵詞是做什么的呢?這很有意思!

在Kotlin語言中,我們必須嚴(yán)格區(qū)分可選值和非可選值。而無論是可選值還是非可選值,在聲明的時候你都必須首先初始化。

那么,如果本身是一個非可選值,但在初始化的時候我們并不知道應(yīng)該賦什么初始值。或者說,我們壓根就不想賦初始值,該怎么辦?lateinit就是用于解決這個問題的。

其實(shí)這個場景的確廣泛存在,比如這個變量是一個對象類型的數(shù)據(jù)。很明顯,給一個對象變量賦予一個初始值的意義不大。因此,你可以選擇使用lateinit修飾這個變量??墒?,與此同時,你的災(zāi)難也降臨了!

群里同學(xué)反饋多次的一個問題就是:提示變量沒有初始化。

其實(shí),本身這個問題并不難,但難的是你要完全弄清楚使用lateinit的前提。如果你決定使用lateinit,你至少應(yīng)該記住下面兩個規(guī)則:

  1. lateinit只能用于修飾非可選值。因此,必須確保你的這個變量在任何時候都不會被賦值為空。
  2. lateinit表示這個變量的初始化可能發(fā)生在任何時候。因此。使用lateinit之前,問一問自己。你是否非常清楚你一定會在使用這個變量之前將其進(jìn)行初始化。

為了避免因?yàn)槲闯跏蓟鸬漠惓栴},Kotlin語言為每一個lateini屬性實(shí)例提供了一個判斷是否已經(jīng)初始化的屬性值isInitialized。因此,為了避免出現(xiàn)初始化問題,你最好判斷一下這個變量是否已經(jīng)完成初始化:

private lateinit var dog: Dog
if (::dog.isInitialized) {
    ....
}

非可選值中的空指針陷阱

部分同學(xué)喜歡這樣聲明數(shù)據(jù)類:

data class Ticket(var id: Long, var name: String ...) 

對于客戶端類應(yīng)用,數(shù)據(jù)類通常對應(yīng)后臺返回的一段Json字符串。那么,悲劇又誕生了!如果后臺沒有返回name字段,Json框架在進(jìn)行數(shù)據(jù)解析的時候認(rèn)為name為空值,嘗試將其賦值為空。不可預(yù)料地,臭名昭著的空指針異常又出現(xiàn)了。

因此,記住一個原則:除非你確定這個變量一定不會被賦值為空。否則,請盡量使用可選值。

可選值中的空指針陷阱

類似地,在可選值中也存在著空指針陷阱。而因?yàn)槭艿絁ava語言的影響,這個部分出現(xiàn)空指針異常的概率更高。看下面的例子:

var isRight: Boolean? = null

if (isRight!!) {
   ...
}

對于上面的代碼,Kotlin將毫不留情地拋給你一個空指針異常。比Java空指針異常更溫柔的是,這個空指針異常的名稱叫做KotlinNullPointerException。

因此,記住一個原則,如果使用可選值需要進(jìn)行解包的時候。一定要確定這個可選值此刻是有值的。針對上面這個例子,更好的處理方式應(yīng)該是這樣:

var isRight: Boolean? = null

if (isRight ?: false) {
   ...
}

不要誤會,我沒有基本數(shù)據(jù)類型

Kotlin認(rèn)為所謂的基本數(shù)據(jù)類型,所謂的拆包,封包是沒有意義的。因此,在Kotlin語言中所有的基本數(shù)據(jù)類型變量也是對象,擁有與變量一樣的行為。

所以,記住一個原則,從Java轉(zhuǎn)換到Kotlin,在使用基本數(shù)據(jù)類型變量的時候同樣需要注意合理地選擇可選值和非可選值,慎用lateinit。

雙冒號到底是個什么東西

雙冒號(::)操作符是Kotlin語言特有的操作符。它主要有以下幾個作用:

  1. 獲取KClass引用
  2. 獲取函數(shù)引用
  3. 獲取屬性引用
  4. 獲取構(gòu)造函數(shù)引用

獲取KClass引用

這是很常用的表達(dá)式,不過通常用于獲取java的Class實(shí)例:

val javaClass = Person::class.java

注:這在Android開發(fā)中比較常用,通常用于獲取Activity的Java class實(shí)例。

獲取函數(shù)引用

在Kotlin語言中,你可以使用函數(shù)作為某個高階函數(shù)的參數(shù)。使用雙冒號操作符可以用于獲取具體的函數(shù)引用作為參數(shù)傳入目標(biāo)函數(shù):

fun cdn(x: Int): Boolean {
    return x >= 3
}

fun filter(x: Int, condition: (x: Int)->Boolean): Boolean {
    return condition(x)
}

filter(5, ::cdn)

獲取屬性引用

Kotlin類中每一個成員變量對應(yīng)一個Property實(shí)例,使用雙冒號操作符可以用于獲取該屬性實(shí)例。在lateinit場景中,這很有用!

class Dog {
    var name: String? = null
}
// 注意:這里獲取的是Property實(shí)例,而非屬性本身
val property = Dog::name

val receiver = Dog()
println(property.get(receiver))

注:類對象變量本身并沒有isInitialized屬性,要判斷l(xiāng)ateinit變量是否已經(jīng)完成初始化,需要通過雙冒號獲取該變量對應(yīng)的Property實(shí)例才能判斷。

獲取構(gòu)造函數(shù)引用

雙冒號操作符也可以用于獲取某個對象的構(gòu)造函數(shù)實(shí)例,具體的用法是:在類名稱前面使用雙冒號。看下面的例子:

class Dog {
    var name: String? = null
}

val init = ::Dog
val dog = init()
println(dog.name)

注:該構(gòu)造函數(shù)實(shí)例同樣可以作為參數(shù)傳入某個高階函數(shù)中。

PS:雙冒號操作符其實(shí)就是用于簡化Kotlin反射而創(chuàng)造的一種操作符。

簡單總結(jié)

你在日常使用Kotlin語言的過程中還有遇到其它問題嗎?如果有,請留言告訴我!

歡迎加入Kotlin交流群

如果你也喜歡Kotlin語言,歡迎加入我的Kotlin交流群: 329673958 ,一起來參與Kotlin語言的推廣工作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容