Kotlin ?!! 這些符號到底什麼意思

Jui Yuan Liou
8 min readJun 16, 2018

--

內容農場標題:Kotlin ?!! 這些符號到底是什麼意思?知道答案的瞬間我眼框濕濕的

在學習 Kotlin 時,常常會遇到 ? 與 !! 這兩種符號,本質上這是 Kotlin 提供的 Null Safety 特性,主要想解決 Null Pointer Exception 的發生。討論這兩個符號,連帶的需要討論 valvar 這兩種宣告變數的方式。

宣告變數

Kotlin 有valvar 兩種宣告變數的方式, 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.")
}

--

--

No responses yet