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造成的問題與負擔。