在筆者的Kotlin交流羣裏,很多同窗反覆遇到了一些類似的問題。這些問題大都比較基礎,但又容易產生誤解。所以,我決定寫一篇文章,整理羣裏同窗遇到的一些問題java
在Kotlin語言中,咱們使用var聲明變量,使用val聲明常量。因爲來自Java語言中沒有區分常量變量的影響,一些同窗對這兩個關鍵字的理解有問題。爲了理解這兩個變量的區別,咱們能夠用兩個等式來講明一下:程序員
var str: String = "abc" => public String str = "abc"
val str: String = "abc" => public final String str = "abc"
複製代碼
=>符號後面是對應的Java代碼,Java語言使用final關鍵字聲明常量。很明顯,使用明確的變量和常量聲明更有助於理解。編程
注:一些Java程序員不多使用final關鍵字,這說明這部分同窗對於常量的使用不太理解。事實上,JVM中有一個常量池,若是發現常量池中存在該值就直接使用;反之,則建立並存入常量池。從這個層面來講,使用常量比使用變量效率更高。更重要的是,若是你聲明一個不會被改動的變量,使用final修飾將更準確,也更安全。安全
其實,在使用Kotlin語言的這兩年裏,我歷來沒有用過這個關鍵詞。但剛剛接觸Kotlin語言的同窗彷佛很喜歡使用這個修飾符修飾變量。bash
這個關鍵詞是作什麼的呢?這頗有意思!框架
在Kotlin語言中,咱們必須嚴格區分可選值和非可選值。而不管是可選值仍是非可選值,在聲明的時候你都必須首先初始化。函數
那麼,若是自己是一個非可選值,但在初始化的時候咱們並不知道應該賦什麼初始值。或者說,咱們壓根就不想賦初始值,該怎麼辦?lateinit就是用於解決這個問題的。ui
其實這個場景的確普遍存在,好比這個變量是一個對象類型的數據。很明顯,給一個對象變量賦予一個初始值的意義不大。所以,你能夠選擇使用lateinit修飾這個變量。但是,與此同時,你的災難也降臨了!spa
羣裏同窗反饋屢次的一個問題就是:提示變量沒有初始化。指針
其實,自己這個問題並不難,但難的是你要徹底弄清楚使用lateinit的前提。若是你決定使用lateinit,你至少應該記住下面兩個規則:
爲了不由於未初始化引發的異常問題,Kotlin語言爲每個lateini屬性實例提供了一個判斷是否已經初始化的屬性值isInitialized。所以,爲了不出現初始化問題,你最好判斷一下這個變量是否已經完成初始化:
private lateinit var dog: Dog
if (::dog.isInitialized) {
....
}
複製代碼
部分同窗喜歡這樣聲明數據類:
data class Ticket(var id: Long, var name: String ...)
複製代碼
對於客戶端類應用,數據類一般對應後臺返回的一段Json字符串。那麼,悲劇又誕生了!若是後臺沒有返回name字段,Json框架在進行數據解析的時候認爲name爲空值,嘗試將其賦值爲空。不可預料地,臭名昭著的空指針異常又出現了。
所以,記住一個原則:除非你肯定這個變量必定不會被賦值爲空。不然,請儘可能使用可選值。
相似地,在可選值中也存在着空指針陷阱。而由於受到Java語言的影響,這個部分出現空指針異常的機率更高。看下面的例子:
var isRight: Boolean? = null
if (isRight!!) {
...
}
複製代碼
對於上面的代碼,Kotlin將絕不留情地拋給你一個空指針異常。比Java空指針異常更溫柔的是,這個空指針異常的名稱叫作KotlinNullPointerException。
所以,記住一個原則,若是使用可選值須要進行解包的時候。必定要肯定這個可選值此刻是有值的。針對上面這個例子,更好的處理方式應該是這樣:
var isRight: Boolean? = null
if (isRight ?: false) {
...
}
複製代碼
Kotlin認爲所謂的基本數據類型,所謂的拆包,封包是沒有意義的。所以,在Kotlin語言中全部的基本數據類型變量也是對象,擁有與變量同樣的行爲。
因此,記住一個原則,從Java轉換到Kotlin,在使用基本數據類型變量的時候一樣須要注意合理地選擇可選值和非可選值,慎用lateinit。
雙冒號(::)操做符是Kotlin語言特有的操做符。它主要有如下幾個做用:
這是很經常使用的表達式,不過一般用於獲取java的Class實例:
val javaClass = Person::class.java
複製代碼
注:這在Android開發中比較經常使用,一般用於獲取Activity的Java class實例。
在Kotlin語言中,你可使用函數做爲某個高階函數的參數。使用雙冒號操做符能夠用於獲取具體的函數引用做爲參數傳入目標函數:
fun cdn(x: Int): Boolean {
return x >= 3
}
fun filter(x: Int, condition: (x: Int)->Boolean): Boolean {
return condition(x)
}
filter(5, ::cdn)
複製代碼
Kotlin類中每個成員變量對應一個Property實例,使用雙冒號操做符能夠用於獲取該屬性實例。在lateinit場景中,這頗有用!
class Dog {
var name: String? = null
}
// 注意:這裏獲取的是Property實例,而非屬性自己
val property = Dog::name
val receiver = Dog()
println(property.get(receiver))
複製代碼
注:類對象變量自己並無isInitialized屬性,要判斷lateinit變量是否已經完成初始化,須要經過雙冒號獲取該變量對應的Property實例才能判斷。
雙冒號操做符也能夠用於獲取某個對象的構造函數實例,具體的用法是:在類名稱前面使用雙冒號。看下面的例子:
class Dog {
var name: String? = null
}
val init = ::Dog
val dog = init()
println(dog.name)
複製代碼
注:該構造函數實例一樣能夠做爲參數傳入某個高階函數中。
PS:雙冒號操做符其實就是用於簡化Kotlin反射而創造的一種操做符。
你在平常使用Kotlin語言的過程當中還有遇到其它問題嗎?若是有,請留言告訴我!
若是你也喜歡Kotlin語言,歡迎加入個人Kotlin交流羣: 329673958 ,一塊兒來參與Kotlin語言的推廣工做。