Java與Kotlin如何面對null
張益裕Michael Chang
- 恆逸教育訓練中心-資深講師
- 技術分類:程式設計
英國的計算機科學家Sir Charles Antony Richard Hoare,通常被稱為Tony Hoare或C. A. R. Hoare。在2009年的一次會議,他為發明null reference而道歉。下面是原文翻譯後的內容:
「我稱之為「十億美元的錯誤」。我在1965年發明null reference。當時,我正在設計第一個物件導向程式設計語言(ALGOL W)的綜合型態系統。 我的目標是確保所有物件的使用都應該是絕對安全的,並且由編譯器自動執行檢查。但是,我無法抗拒使用null reference的誘惑,因為它很容易實現。這導致了無數的錯誤,漏洞和系統崩潰,這可能在過去四十年中造成了十億美元的痛苦和破壞。」
如果一個物件變數的值為null,在使用它的時候(例如呼叫函式),就會發生NullPointerException執行時期例外,如果您沒有執行額外的控制,就會導致程式中斷並結束。應用程式運作的時候發生NullPointerException問題還挺常見的,所以大家使用比較簡短的名稱NPE稱呼它。
null reference已經是程式設計師根深蒂固的概念,例如下面這個函式:
public static String getName(int index) { String result = null; if (index == 3) result = "Simon"; return result; }
如果呼叫這個函式的時候沒有特別處理,就可能會發生NPE:
String name = getName(2); System.out.println(name.toUpperCase()); // NPE!!
所以程式設計師執行必要的判斷後,才可以排除產生NPE的可能性:
String name = getName(2); if (name != null) { System.out.println(name.toUpperCase()); }
在Java API中也有很多這類的設計,例如Map的get函式,通常我們在設計一個功能的時候,都會很直覺的使用這樣的想法:如果成功就傳回結果,如果不成功就傳回null。Java為了解決這類的問題,在Java 8加入「java.util.Optional」,把想法轉換為「如果成功就傳回包裝結果的Optional物件,如果不成功就傳回空的Optional物件」。例如下面採用Optional概念設計的函式:
public static OptionalgetNameOpt(int index) { Optional result = Optional.empty(); if (index == 3) result = Optional.of("Simon"); return result; }
因為執行後的結果包裝在Optional物件,所以工作會比較多一些:
Optionalopt = get("X"); if ( opt.isPresent() ) { String v = opt.get(); // do something with v } else { // do something else }
不過Optional提供許多方便的函式,也可以搭配Lambda提供更強大的功能。例如下面的範例,可以在Optional沒有包裝結果的時候指定一個預設值:
Optionalopt = get("X"); String v = opt.orElse("Not exists"); // do something with v
Kotlin是一個比較新的JVM程式語言,在2016年2月發表v1.0,2017年5月的Google開發人員大會,發佈Kotlin為Android官方的程式設計語言。從Android Studio 3開始,開發人員可以直接使用Kotlin開發Android App。
Kolin延續Java許多優點,也改進許多Java原有的缺點,尤其是Kotlin在面對null的時候,採用跟Java完全不一樣的想法。Kotlin在宣告物件變數的時候,預設的作法是不允許指定null值給物件變數:
var name: String = "Simon" name = null // 編譯錯誤,不可以指定null值 println(name.toUpperCase()) // 可以直接呼叫函式
如果為了特殊的需求,您也可在宣告物變數的時候,讓它可以指定null值。不過為了預防因為null造成的例外,Kotlin針對允許null值的變數設計了一些作法:
var name2: String? = "Mary" // 宣告可以指定null值的變數 name2 = null // 可以指定null值 println(name2.length) // 編譯錯誤 if (name2 != null) // 如果先經過null值的判斷... println(name2.length) // 就可以直接使用
您可以把Kotlin對於null的態度延伸到自己的設計中:
fun getName(index: Int): String? { // 回傳允許null的String var result: String? = null if (index == 3) result = "Simon" return result }
您在呼叫上面函式並處理回傳值的時候,就必須執行明確的處理:
// val name: String = getName(2) // 編譯錯誤 val name: String? = getName(2) if (name != null) println(name.toUpperCase())
Kotlin從底層開始面對null reference,再延伸到程式語言的結構與設計的方式,讓程式設計師可以用比較簡潔與安全的方式,排除因為null造成的問題與負擔。