在 Kotlin 中使用是否可為空值

1. 事前準備

在本程式碼實驗室中,您將瞭解「是否可為空值」相關的知識以及 null 安全的重要性。「是否可為空值」是許多程式設計語言中常見的概念,代表您可以不對變數設定任何值。在 Kotlin 中,我們特別設計了對「是否可為空值」的處理機制來確保 null 安全。

必要條件

  • 具備 Kotlin 程式設計的基本知識,包括變數、如何存取變數中的方法和屬性,以及 println()main() 函式
  • 熟悉 Kotlin 條件式,包含 if/else 陳述式和布林運算式

課程內容

  • 什麼是 null
  • 可為空值和不可為空值類型之間的差異。
  • 什麼是 null 安全、其重要性,以及 Kotlin 如何確保 null 安全。
  • 如何使用 ?. 安全呼叫運算子和 !! 非空值斷言運算子,來存取可為空值變數的方法和屬性。
  • 如何使用 if/else 條件式執行 null 檢查。
  • 如何使用 if/else 運算式將可為空值變數轉換成不可為空值類型。
  • 如何使用 if/else 運算式或 ?: Elvis 運算子,在可為空值變數為 null 時提供預設值。

軟硬體需求

  • 可使用 Kotlin Playground 的網路瀏覽器。

2. 使用可為空值變數

什麼是 null

在單元 1 中,我們已經瞭解到在宣告變數時,必須立即為其指派一個值。舉例來說,當宣告 favoriteActor 變數時,可以立即為其指派 "Sandra Oh" 字串值。

val favoriteActor = "Sandra Oh"

這個方塊代表為 favoriteActor 變數指派了

不過,沒有喜愛的演員時該怎麼辦?您可能想為變數指派 "Nobody""None" 值,但這並不是好方法,因為程式會將 favoriteActor 變數解讀為含有 "Nobody""None" 值,而不是完全沒有值。在 Kotlin 中,可以使用 null 表明變數沒有任何相關聯的值。

這個方塊代表為 afavoriteActor 變數指派了空值。

如要在程式碼中使用 null,請按照下列步驟操作:

  1. Kotlin Playground 中,將 main() 函式主體中的內容替換成設為 nullfavoriteActor 變數:
fun main() {
    val favoriteActor = null
}
  1. 使用 println() 函式輸出 favoriteActor 變數的值,然後執行以下程式:
fun main() {
    val favoriteActor = null
    println(favoriteActor)
}

輸出結果如以下程式碼片段所示:

null

為變數重新指派 null

在前面的課程中,您已瞭解可以為使用 var 關鍵字定義的變數重新指派相同類型的不同值。舉例來說,您可以為已使用某個名稱宣告的 name 變數重新指派另一個名稱,只要新名稱為 String 類型即可。

var favoriteActor: String = "Sandra Oh"
favoriteActor = "Meryl Streep"

有時候,您在宣告變數後可能會想為該變數指派 null。舉例來說,在宣告喜愛的演員後,您發現自己根本不想公開自己喜愛的演員。在這種情況下,為 favoriteActor 變數指派 null 會很有用。

瞭解不可為空值和可為空值的變數

如要為 favoriteActor 變數重新指派 null,請按照下列步驟操作:

  1. val 關鍵字變更為 var 關鍵字,然後將 favoriteActor 變數指定為 String 類型,並為其指派自己喜愛演員的名字:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor)
}
  1. 移除 println() 函式:
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. favoriteActor 變數重新指派 null,然後執行以下程式:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    favoriteActor = null
}

系統會顯示下列錯誤訊息:

顯示的警告訊息如下:

在 Kotlin 中,變數有可為空值和不可為空值類型之分:

  • 可為空值類型是「可以」儲存 null 值的變數。
  • 不可為空值類型是「無法」儲存 null 值的變數。

只有當您明確允許某個變數儲存 null 值時,該變數才屬於可為空值類型。如錯誤訊息所示,String 資料類型屬於不可為空值類型,因此您無法為該變數重新指派 null

這張圖表展示如何宣告可為空值類型變數。該變數開頭為 var 關鍵字,後面接著變數區塊名稱、分號、變數類型、問號、等號和值區塊。類型區塊和問號標示「可為空值類型」文字,這代表該類型後方加上問號後即變成可為空值類型。

如要在 Kotlin 中宣告可為空值變數,您需要在類型結尾加上 ? 運算子。舉例來說,String? 類型可儲存字串或 null 值,而 String 類型只能儲存字串。如要宣告可為空值變數,您必須明確新增可為空值類型。如果沒有可為空值類型,Kotlin 編譯器會推論該變數屬於不可為空值類型。

  1. favoriteActor 變數類型從 String 資料類型變更為 String? 資料類型:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    favoriteActor = null
}
  1. 在重新指派 null 前後輸出 favoriteActor 變數,然後執行以下程式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor)

    favoriteActor = null
    println(favoriteActor)
}

輸出結果如以下程式碼片段所示:

Sandra Oh
null

favoriteActor 變數原本儲存的是字串,接著重新指派了 null

試試看

現在,您已瞭解如何使用可為空值的 String? 類型,那麼現在可以透過 Int 值將變數初始化,並為其重新指派 null 嗎?

寫入可為空值的 Int

  1. 移除 main() 函式中的所有程式碼:
fun main() {
    
}
  1. 建立一個 number 變數,其中含有可為空值的 Int 類型,然後為該變數指派 10 值:
fun main() {
    var number: Int? = 10
}
  1. 輸出 number 變數,然後執行以下程式:
fun main() {
    var number: Int? = 10
    println(number)
}

輸出內容與預期結果相符,如下所示:

10
  1. number 變數重新指派 null,以確認變數可為空值:
fun main() {
    var number: Int? = 10
    println(number)
    
    number = null
}
  1. 在程式的最後一行新增其他 println(number) 陳述式,然後執行此程式:
fun main() {
    var number: Int? = 10
    println(number)
    
    number = null
    println(number)
}

輸出內容與預期結果相符,如下所示:

10
null

3. 處理可為空值變數

在前面的課程中,您已學習如何使用 . 運算子存取不可為空值變數的方法和屬性。本節將說明如何使用這個運算子存取可為空值變數的方法和屬性。

如要存取不可為空值的 favoriteActor 變數的屬性,請按照下列步驟操作:

  1. 移除 main() 函式中的所有程式碼,然後宣告 String 類型的 favoriteActor 變數,並為其指派喜愛演員的名字:
fun main() {
    var favoriteActor: String = "Sandra Oh"
}
  1. 使用 length 屬性輸出 favoriteActor 變數值中的字元數,然後執行以下程式:
fun main() {
    var favoriteActor: String = "Sandra Oh"
    println(favoriteActor.length)
}

輸出內容與預期結果相符,如下所示:

9

favoriteActor 變數的值中有「9」個字元 (包括空格)。您喜愛的演員名字中的字元數可能有所不同。

存取可為空值變數的屬性

假設您要讓 favoriteActor 變數設為可為空值,以便讓沒有喜愛演員的使用者能為變數指派 null

如要存取可為空值的 favoriteActor 變數的屬性,請按照下列步驟操作:

  • favoriteActor 變數類型變更為可為空值類型,然後執行以下程式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor.length)
}

系統會顯示下列錯誤訊息:

顯示的錯誤訊息如下:

這類錯誤屬於「編譯錯誤」。如先前的程式碼實驗室所述,當程式碼中有語法錯誤,導致 Kotlin 無法編譯程式碼時,就會發生編譯錯誤。

Kotlin 會刻意套用語法規則,以確保 null「安全」,即保證「不會意外呼叫 null 變數」。然而,這並不表示變數不能為 null,而是表示當您存取變數成員時,該變數不能為 null

這點非常重要,因為如果在應用程式執行期間嘗試存取 null 變數成員 (稱為「null 參照」),應用程式會因 null 變數不含任何屬性或方法而停止運作。這類停止運作的狀況稱為「執行階段錯誤」,即在程式碼完成編譯及執行後發生的錯誤。

由於 Kotlin 具有確保 null 安全的特性,因此 Kotlin 編譯器會對可為空值類型強制執行「null 檢查」,以免發生這類執行階段錯誤。「Null 檢查」是指在存取變數並將其視為不可為空值類型之前,先檢查變數是否可為 null 的程序。如果您要將可為空值的值當做不可為空值類型使用,就必須明確執行 null 檢查。如要瞭解相關資訊,請參閱本程式碼實驗室後續的「使用 if/else 條件式」一節。

在本範例中,系統不允許直接參照 favoriteActor 變數的 length 屬性 (因為該變數有可能是 null),這會導致程式碼在編譯時間執行失敗。

接下來,您將瞭解用來處理可為空值類型的各種技巧和運算子。

使用 ?. 安全呼叫運算子

您可以使用 ?. 安全呼叫運算子,以存取可為空值變數的方法或屬性。

這張圖表顯示可為空值變數區塊,後面依序接有問號、點以及方法或屬性區塊。這些內容之間沒有空格。

如要使用 ?. 安全呼叫運算子存取方法或屬性,請在變數名稱後方加上 ? 符號,並使用 . 標記存取方法或屬性。

?. 安全呼叫運算子可讓您安全存取可為空值變數,因為 Kotlin 編譯器會阻止變數成員存取 null 參照,並對存取的成員回傳 null

如要安全存取可為空值的 favoriteActor 變數屬性,請按照下列步驟操作:

  1. println() 陳述式中,將 . 運算子替換成 ?. 安全呼叫運算子:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor?.length)
}
  1. 執行以下程式,並確認輸出內容是否符合預期:
9

您喜愛的演員名字中的字元數可能有所不同。

  1. favoriteActor 變數重新指派 null,然後執行以下程式:
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor?.length)
}

輸出內容如下:

null

請注意,即使嘗試存取 null 變數的 length 屬性,該程式仍不會停止運作。安全呼叫運算式只會回傳 null

使用 !! 非空值斷言運算子

您也可以使用 !! 非空值斷言運算子,來存取可為空值變數的方法或屬性。

這張圖表展示可為空值變數區塊,後面依序接有兩個驚嘆號、一個點以及方法或屬性區塊。這些內容之間沒有空格。

您需要在可為空值變數之後加上 !! 非空值斷言運算子,之後跟著 . 運算子,最後是方法或屬性 (不含任何空格)。

顧名思義,如果您使用 !! 非空值斷言運算子,即表示您聲明變數的值並非 null,無論變數是否為該值都是如此。

?. 安全呼叫運算子不同,如果可為空值變數確實是 null,使用 !! 非空值斷言運算子可能會導致系統擲回 NullPointerException 錯誤。因此,只有在變數始終為不可為空值,或已設定適當的例外狀況處理方式時,才應使用該斷言運算子。如果例外狀況未得到妥善處理,會造成執行階段錯誤。本課程的後續單元將說明例外狀況處理方式。

如要使用 !! 非空值斷言運算子存取 favoriteActor 變數的屬性,請按照下列步驟操作:

  1. favoriteActor 變數重新指派您喜愛的演員名字,然後在 println() 陳述式中將 ?. 安全呼叫運算子替換成 !! 非空值斷言運算子:
fun main() {
    var favoriteActor: String? = "Sandra Oh"
    println(favoriteActor!!.length)
}
  1. 執行以下程式,並確認輸出內容是否符合預期:
9

您喜愛的演員名字中的字元數可能有所不同。

  1. favoriteActor 變數重新指派 null,然後執行以下程式:
fun main() {
    var favoriteActor: String? = null
    println(favoriteActor!!.length)
}

系統會顯示 NullPointerException 錯誤,內容如下:

顯示的錯誤訊息如下:

這個 Kotlin 錯誤顯示,您的程式在執行期間停止運作。因此,除非您確定變數不是 null,否則不建議使用 !! 非空值斷言運算子。

使用 if/else 條件式

您可以在 if/else 條件式中使用 if 分支版本來執行「null 檢查」

這張圖表展示可為空值變數區塊,後面依序接有驚嘆號、等號和空值。

如要執行 null 檢查,可以使用 != 比較運算子檢查可為空值變數是否不等於 null

if/else 陳述式

if/else 陳述式可與 null 檢查搭配使用,如下所示:

這張圖表說明 if/else 陳述式的結構。開頭是 if 關鍵字,後面依序是加上括號的空值檢查區塊、加上一組大括號的主體 1、else 關鍵字,然後是加上另一組大括號的主體 2 區塊。else 子句以紅色虛線方塊框住,並標註為選用項目。

空值檢查與 if/else 陳述式搭配使用時有以下優點:

  • nullableVariable != null 運算式的 null 檢查會用作 if 條件式。
  • if 分支版本內的主體 1 會假設變數不可為 null。因此,在這個主體中,您可以自由存取變數的方法或屬性,就好像該變數是不可為空值變數一般,而無需使用 ?. 安全呼叫運算子或 !! 非空值斷言運算子。
  • else 分支版本內的主體 2 會假設變數為 null。因此,在這個主體中,您可以新增在變數為 null 時執行的陳述式。else 分支版本為選用項目。當 null 檢查失敗時,您只能使用 if 條件式來執行 null 檢查,且不執行預設動作。

如果有多行程式碼使用可為空值變數,那麼將 null 檢查與 if 條件式搭配使用較為簡便。相比之下,?. 安全呼叫運算子則適合用於可為空值變數的單一參照。

如要編寫為 if/else 陳述式來對 favoriteActor 變數執行 null 檢查,請按照下列步驟操作:

  1. 再次為 favoriteActor 變數指派您喜愛的演員名字,然後移除 println() 陳述式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

}
  1. 新增包含 favoriteActor != null 條件式的 if 分支版本:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {

    }
}
  1. if 分支版本的主體中,新增可接受 "The number of characters in your favorite actor's name is ${favoriteActor.length}." 字串的 println 陳述式,然後執行以下程式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    }
}

輸出內容與預期結果相符。

The number of characters in your favorite actor's name is 9.

您喜愛的演員名字中的字元數可能有所不同。

請注意,由於您會在 null 檢查後存取 if 分支版本內的 length 方法,因此可直接使用 . 運算子存取名字長度方法。同時,Kotlin 編譯器知道 favoriteActor 變數絕不會是 null,所以允許直接存取屬性。

  1. 選用:新增 else 分支版本,以處理演員名字為 null 的情形:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {

    }
}
  1. else 分支版本的主體中,新增可接受 "You didn't input a name." 字串的 println 陳述式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. favoriteActor 變數指派 null,然後執行以下程式:
fun main() {
    var favoriteActor: String? = null

    if(favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}

輸出內容與預期結果相符,如下所示:

You didn't input a name.

if/else 運算式

您還可以將 null 檢查與 if/else 運算式結合,藉此將可為空值變數轉換成不可為空值變數。

這張圖表說明 if/else 運算式的結構。開頭是 val 關鍵字,後面依序接有名稱區塊、分號、非空值類型區塊、等號、if 關鍵字、加上括號的條件、加上一組大括號的主體 1、else 關鍵字以及加上另一組大括號的主體 2 區塊。

如要為 if/else 運算式指派不可為空值類型,請按照下列步驟操作:

  • nullableVariable != null null 檢查用作 if 條件式。
  • if 分支版本內的主體 1 會假設變數不可為 null。因此,在這個主體中,您可以存取變數的方法或屬性,就好像該變數是不可為空值變數一般,而無需使用 ?. 安全呼叫運算子或 !! 非空值斷言運算子。
  • else 分支版本內的主體 2 會假設變數為 null。因此,在這個主體中,您可以新增在變數為 null 時執行的陳述式。
  • 在主體 1 和主體 2 的最後一行中,您必須使用會產生不可為空值類型的運算式或值,這樣一來,就能在 null 檢查通過或失敗時指派給不可為空值變數。

如要使用 if/else 運算式重新編寫程式,讓程式只使用一個 println 陳述式,請按照下列步驟操作:

  1. favoriteActor 變數指派您喜愛的演員名字:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. 建立 lengthOfName 變數,然後為其指派 if/else 運算式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      println("The number of characters in your favorite actor's name is ${favoriteActor.length}.")
    } else {
      println("You didn't input a name.")
    }
}
  1. ifelse 這兩個分支版本中移除 println() 陳述式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      
    } else {
      
    }
}
  1. if 分支版本的主體中新增 favoriteActor.length 運算式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      favoriteActor.length
    } else {
      
    }
}

favoriteActor 變數的 length 屬性可直接使用 . 運算子存取。

  1. else 分支版本的主體中新增 0 值:
fun main() {
   var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }
}

當名字為 null 時,0 值會成為預設值。

  1. main() 函式的結尾加上可接受 "The number of characters in your favorite actor's name is $lengthOfName." 字串的 println 陳述式,然後執行以下程式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = if (favoriteActor != null) {
      favoriteActor.length
    } else {
      0
    }

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

輸出內容與預期結果相符,如下所示:

The number of characters in your favorite actor's name is 9.

您使用的名稱字元數可能有所不同。

使用 ?: Elvis 運算子

?: Elvis 運算子是可與 ?. 安全呼叫運算子搭配使用的運算子。使用 ?: Elvis 運算子時,您可以在 ?. 安全呼叫運算子回傳 null 時加上預設值。這個做法與 if/else 運算式類似,但更為常用。

如果變數「不是」null,系統會執行 ?: Elvis 運算子之前的運算式。如果變數「是」null,系統會執行 ?: Elvis 運算子之後的運算式。

這張圖表顯示 val 關鍵字,後面依序接有名稱區塊、等號、可為空值變數區塊、問號、點、方法或屬性區塊、問號、冒號和預設值區塊。

如要修改先前的程式以使用 ?: Elvis 運算子,請按照下列步驟操作:

  1. 移除 if/else 條件式,然後將 lengthOfName 變數設為可為空值的 favoriteActor 變數,並使用 ?. 安全呼叫運算子呼叫其 length 屬性:
fun main() {
   var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}
  1. length 屬性之後加上 ?: Elvis 運算子以及 0 值,然後執行以下程式:
fun main() {
    var favoriteActor: String? = "Sandra Oh"

    val lengthOfName = favoriteActor?.length ?: 0

    println("The number of characters in your favorite actor's name is $lengthOfName.")
}

輸出內容會與先前的輸出內容相同:

The number of characters in your favorite actor's name is 9.

4. 結語

恭喜!您已學會是否可為空值的相關知識,以及如何運用各種運算子進行管理。

摘要

  • 可將變數設為 null,代表該變數不儲存任何值。
  • 不可將 null 指派給不可為空值變數。
  • 可將 null 指派給可為空值變數。
  • 如要存取可為空值變數的方法或屬性,需要使用 ?. 安全呼叫運算子或 !! 非空值斷言運算子。
  • 可以將 if/else 陳述式與 null 檢查搭配使用,以在不可為空值的結構定義中存取可為空值變數。
  • 可使用 if/else 運算式將可為空值變數轉換成不可為空值類型。
  • 可以使用 if/else 運算式或 ?: Elvis 運算子,在可為空值變數為 null 時,提供預設值。

瞭解詳情