Kotlin ?!! 這些符號到底什麼意思
在學習 Kotlin 時,常常會遇到 ? 與 !! 這兩種符號,本質上這是 Kotlin 提供的 Null Safety 特性,主要想解決 Null Pointer Exception 的發生。討論這兩個符號,連帶的需要討論 val
和 var
這兩種宣告變數的方式。
宣告變數
Kotlin 有val
與 var
兩種宣告變數的方式, val
變數類似 Java 之中, final
宣告的變數,它不可以再被 assign 新的值,只能取用。通常來說在初始化時決定變數內容;而 var
則和過去面對 Java 參數宣告時類似,都是可以後來再另外赴值的。值得提到的是:Kotlin 必須在宣告 var
變數時,就決定這個變數有沒有可能會是空值 (null)。
val computerBrand = "Asus"// error. val can't be reassigned
computerBrand = "Acer"println(computerBrand)/* just like in Java */
public final String computerBrand = "Asus"// error. can't assign value to final variable
computerBrand = "Acer" System.out.println(computerBrand)
由上方資訊可知: computerBrand
不可能會是 null。
如果宣告一個可為空的變數,就需要在變數類型後方加上「?」。
fun main(args: Array<String>) {
var cellphoneBrand: String? = "Google" // ? means nullable
cellphoneBrand = "Motorola a Lenovo company" // assignable
cellphoneBrand = null // acceptable val mostLoveBrand: String? = "PAWKO"
myPrint(mostLoveBrand)
myPrint(null)
}// like we set @Nullable annotation in Java
fun myPrint(strings: String?) {
val result = strings.reversed() // error
print("Cellphone brand I love $mostLoveBrand most.")
}
在這邊, myPrint()
可傳入變數是 Nullable 的,在反轉 strings
時會出現編譯錯誤。
錯誤訊息是:only safe or non-null asserted are allowed on a nullable receiver of type SomeType(here is String)。
IDE 會提示操作時需要藉由「?」或「!!」去明確表示這個變數的狀態。
用 ? 與 !! 操作變數
從這個階段開始,我們討論的變數以 var
與可為空的變數為主。
以上面的文字反轉後再印出為例,在操作可為空變數時加上「?」與「!!」符號分別代表:
- ?:做 null check 後,不為空的話再執行
- !!:堅持不會是空值,執行就是了
本文第一段提到:宣告一個可為空的變數,就需要在變數類型後方加上「?」。換句話說,這種變數就是處在一個有值又也是空值的疊加狀態,就像那位站車門邊的男性的頭髮,可能是真髮又是假髮的疊加狀態。這時,只能透過觀測來確定變數的狀態(去掀開頭髮)。上面的例子 strings?.reversed()
於變數後方加上「?」再執行函式 (Function),編譯器會檢查變數是不是空的,如果值存在就執行行為,像是這裡的 reversed()
函式 (Function) 如果 strings
有值就會被執行。(站車門邊的男性頭髮可能是真髮可能是假髮,去掀開是真髮,稱讚其髮根強韌。)車門先生?.頭髮?.稱讚真的很強韌呢並比讚()
strings?.reversed()
翻譯回 Java ,會是這個樣子的(透過 Kotlin bytecode 轉換回 Java):
public static final void myPrint(@Nullable String strings) {
String var1;
if (strings != null) {
var1 = StringsKt.reversed((CharSequence)strings).toString();
} else {
var1 = null;
}
String result = var1;
String var2 = "I love " + result + " most.";
System.out.println(var2);
}
「!!」就是比較兇,很強勢!不管這個可為空變數怎麼樣,執行就對了。所以文字反轉印出的範例也能這麼寫,編譯器不會報錯:
fun myPrint(strings: String?) {
val result = strings!!.reversed()
print("Cellphone brand I love $mostLoveBrand most.")
}
但因為 myPrint(strings: String?)
可以接受 null 值傳入, strings
是可為空的疊加狀態。強硬的使用「!!」可能會造成 Null Pointer Exception,所以才會有避免使用「!!」操作變數的講法。將函式 (Function) 改為只接受非空的參數傳入也是一個不用一直判斷是否為空的方法。
可回傳空值的函式
在 Java ,我們能在方法(Method) 加入 @Nullable
或 @NonNull
註解(Annotation) 來讓 IDE 幫助我們提醒呼叫這個方法時有沒有可能會是空值。
而在 Kotlin 之中,一個函式 (Function) 可以決定回傳的參數是否可以為空值。如果可以,使用起來就需要像可為空的變數,需要透過「?」或是「!!」來表示這個函式 (Function) 的回傳值狀態。例如這個例子:
fun addCoolWords(words: String): String {
return "The $words is very cool!!"
}vsfun addSuperCoolWords(words: String): String? {
return "The $words is super cool!!"
}// When calling and reverse the result
// ok
val reversedWords = addCoolWords("Penguin").reversed()// Error. It needs safe call
val reversedSuperWords = addSuperCoolWords("Now").reversed()
在 reversedSuperWords
進行反轉 addSuperCoolWords()
返回的 String 時,會報錯。 因為 addSuperCoolWords()
`回傳的是 String?
`一個可能為空的型別。如果要使用它,則會是這個樣子:
val reversedSuperWords = addSuperCoolWords("Now")?.reversed()
其他補充
很多時候操作可為空的變數或是函式 (Function) 時,會想用類似過去 Java 的三元運算子,如果某個變數或函式 (Function) 是空的話,就給另一個數值。
String oemBrand = thirdPartyBrand != null ? thirdPartyBrand.getName() : "Google";
在 Kotlin 之中,可以用「?:」表達,如果左邊不是空值的話直接回傳左邊的內容,否則回傳右邊的內容。
val oemBrand = thirdPartyBrand?.name ?: "Google"
另外,也能用 Standard Library 提供的 Higher-order functions 判斷變數或是函式 (Function) 是否為空,像是 let、run ⋯ 等等,以後再寫另一篇文章分享。
thirdPartyBrand?.let {
print("OEM Name is ${it.name}")
}// with Null Situation
thirdPartyBrand?.let {
print("OEM Name is ${it.name}")
} ?: run {
print("It\'s from Google.")
}