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 Optional getNameOpt(int index) {
Optional result = Optional.empty();
if (index == 3) result = Optional.of("Simon");
return result;
}
因為執行後的結果包裝在Optional物件,所以工作會比較多一些:
Optional opt = get("X");
if ( opt.isPresent() ) {
String v = opt.get();
// do something with v
}
else {
// do something else
}
不過Optional提供許多方便的函式,也可以搭配Lambda提供更強大的功能。例如下面的範例,可以在Optional沒有包裝結果的時候指定一個預設值:
Optional opt = 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造成的問題與負擔。