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"
不過,沒有喜愛的演員時該怎麼辦?您可能想為變數指派 "Nobody"
或 "None"
值,但這並不是好方法,因為程式會將 favoriteActor
變數解讀為含有 "Nobody"
或 "None"
值,而不是完全沒有值。在 Kotlin 中,可以使用 null
表明變數沒有任何相關聯的值。
如要在程式碼中使用 null
,請按照下列步驟操作:
- 在 Kotlin Playground 中,將
main()
函式主體中的內容替換成設為null
的favoriteActor
變數:
fun main() {
val favoriteActor = null
}
- 使用
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
,請按照下列步驟操作:
- 將
val
關鍵字變更為var
關鍵字,然後將favoriteActor
變數指定為String
類型,並為其指派自己喜愛演員的名字:
fun main() {
var favoriteActor: String = "Sandra Oh"
println(favoriteActor)
}
- 移除
println()
函式:
fun main() {
var favoriteActor: String = "Sandra Oh"
}
- 為
favoriteActor
變數重新指派null
,然後執行以下程式:
fun main() {
var favoriteActor: String = "Sandra Oh"
favoriteActor = null
}
系統會顯示下列錯誤訊息:
在 Kotlin 中,變數有可為空值和不可為空值類型之分:
- 可為空值類型是「可以」儲存
null
值的變數。 - 不可為空值類型是「無法」儲存
null
值的變數。
只有當您明確允許某個變數儲存 null
值時,該變數才屬於可為空值類型。如錯誤訊息所示,String
資料類型屬於不可為空值類型,因此您無法為該變數重新指派 null
。
如要在 Kotlin 中宣告可為空值變數,您需要在類型結尾加上 ?
運算子。舉例來說,String?
類型可儲存字串或 null
值,而 String
類型只能儲存字串。如要宣告可為空值變數,您必須明確新增可為空值類型。如果沒有可為空值類型,Kotlin 編譯器會推論該變數屬於不可為空值類型。
- 將
favoriteActor
變數類型從String
資料類型變更為String?
資料類型:
fun main() {
var favoriteActor: String? = "Sandra Oh"
favoriteActor = null
}
- 在重新指派
null
前後輸出favoriteActor
變數,然後執行以下程式:
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor)
favoriteActor = null
println(favoriteActor)
}
輸出結果如以下程式碼片段所示:
Sandra Oh null
favoriteActor
變數原本儲存的是字串,接著重新指派了 null
。
試試看
現在,您已瞭解如何使用可為空值的 String?
類型,那麼現在可以透過 Int
值將變數初始化,並為其重新指派 null
嗎?
寫入可為空值的 Int
值
- 移除
main()
函式中的所有程式碼:
fun main() {
}
- 建立一個
number
變數,其中含有可為空值的Int
類型,然後為該變數指派10
值:
fun main() {
var number: Int? = 10
}
- 輸出
number
變數,然後執行以下程式:
fun main() {
var number: Int? = 10
println(number)
}
輸出內容與預期結果相符,如下所示:
10
- 為
number
變數重新指派null
,以確認變數可為空值:
fun main() {
var number: Int? = 10
println(number)
number = null
}
- 在程式的最後一行新增其他
println(number)
陳述式,然後執行此程式:
fun main() {
var number: Int? = 10
println(number)
number = null
println(number)
}
輸出內容與預期結果相符,如下所示:
10 null
3. 處理可為空值變數
在前面的課程中,您已學習如何使用 .
運算子存取不可為空值變數的方法和屬性。本節將說明如何使用這個運算子存取可為空值變數的方法和屬性。
如要存取不可為空值的 favoriteActor
變數的屬性,請按照下列步驟操作:
- 移除
main()
函式中的所有程式碼,然後宣告String
類型的favoriteActor
變數,並為其指派喜愛演員的名字:
fun main() {
var favoriteActor: String = "Sandra Oh"
}
- 使用
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
變數屬性,請按照下列步驟操作:
- 在
println()
陳述式中,將.
運算子替換成?.
安全呼叫運算子:
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor?.length)
}
- 執行以下程式,並確認輸出內容是否符合預期:
9
您喜愛的演員名字中的字元數可能有所不同。
- 為
favoriteActor
變數重新指派null
,然後執行以下程式:
fun main() {
var favoriteActor: String? = null
println(favoriteActor?.length)
}
輸出內容如下:
null
請注意,即使嘗試存取 null
變數的 length
屬性,該程式仍不會停止運作。安全呼叫運算式只會回傳 null
。
使用 !!
非空值斷言運算子
您也可以使用 !!
非空值斷言運算子,來存取可為空值變數的方法或屬性。
您需要在可為空值變數之後加上 !!
非空值斷言運算子,之後跟著 .
運算子,最後是方法或屬性 (不含任何空格)。
顧名思義,如果您使用 !!
非空值斷言運算子,即表示您聲明變數的值並非 null
,無論變數是否為該值都是如此。
與 ?.
安全呼叫運算子不同,如果可為空值變數確實是 null
,使用 !!
非空值斷言運算子可能會導致系統擲回 NullPointerException
錯誤。因此,只有在變數始終為不可為空值,或已設定適當的例外狀況處理方式時,才應使用該斷言運算子。如果例外狀況未得到妥善處理,會造成執行階段錯誤。本課程的後續單元將說明例外狀況處理方式。
如要使用 !!
非空值斷言運算子存取 favoriteActor
變數的屬性,請按照下列步驟操作:
- 為
favoriteActor
變數重新指派您喜愛的演員名字,然後在println()
陳述式中將?.
安全呼叫運算子替換成!!
非空值斷言運算子:
fun main() {
var favoriteActor: String? = "Sandra Oh"
println(favoriteActor!!.length)
}
- 執行以下程式,並確認輸出內容是否符合預期:
9
您喜愛的演員名字中的字元數可能有所不同。
- 為
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
陳述式搭配使用時有以下優點:
nullableVariable != null
運算式的null
檢查會用作if
條件式。if
分支版本內的主體 1 會假設變數不可為null
。因此,在這個主體中,您可以自由存取變數的方法或屬性,就好像該變數是不可為空值變數一般,而無需使用?.
安全呼叫運算子或!!
非空值斷言運算子。else
分支版本內的主體 2 會假設變數為null
。因此,在這個主體中,您可以新增在變數為null
時執行的陳述式。else
分支版本為選用項目。當null
檢查失敗時,您只能使用if
條件式來執行null
檢查,且不執行預設動作。
如果有多行程式碼使用可為空值變數,那麼將 null
檢查與 if
條件式搭配使用較為簡便。相比之下,?.
安全呼叫運算子則適合用於可為空值變數的單一參照。
如要編寫為 if/else
陳述式來對 favoriteActor
變數執行 null
檢查,請按照下列步驟操作:
- 再次為
favoriteActor
變數指派您喜愛的演員名字,然後移除println()
陳述式:
fun main() {
var favoriteActor: String? = "Sandra Oh"
}
- 新增包含
favoriteActor != null
條件式的if
分支版本:
fun main() {
var favoriteActor: String? = "Sandra Oh"
if (favoriteActor != null) {
}
}
- 在
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
,所以允許直接存取屬性。
- 選用:新增
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 {
}
}
- 在
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.")
}
}
- 為
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
運算式指派不可為空值類型,請按照下列步驟操作:
- 將
nullableVariable != null
null
檢查用作if
條件式。 if
分支版本內的主體 1 會假設變數不可為null
。因此,在這個主體中,您可以存取變數的方法或屬性,就好像該變數是不可為空值變數一般,而無需使用?.
安全呼叫運算子或!!
非空值斷言運算子。else
分支版本內的主體 2 會假設變數為null
。因此,在這個主體中,您可以新增在變數為null
時執行的陳述式。- 在主體 1 和主體 2 的最後一行中,您必須使用會產生不可為空值類型的運算式或值,這樣一來,就能在
null
檢查通過或失敗時指派給不可為空值變數。
如要使用 if/else
運算式重新編寫程式,讓程式只使用一個 println
陳述式,請按照下列步驟操作:
- 為
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.")
}
}
- 建立
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.")
}
}
- 從
if
和else
這兩個分支版本中移除println()
陳述式:
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
} else {
}
}
- 在
if
分支版本的主體中新增favoriteActor.length
運算式:
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
favoriteActor.length
} else {
}
}
favoriteActor
變數的 length
屬性可直接使用 .
運算子存取。
- 在
else
分支版本的主體中新增0
值:
fun main() {
var favoriteActor: String? = "Sandra Oh"
val lengthOfName = if (favoriteActor != null) {
favoriteActor.length
} else {
0
}
}
當名字為 null
時,0
值會成為預設值。
- 在
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 運算子之後的運算式。
如要修改先前的程式以使用 ?:
Elvis 運算子,請按照下列步驟操作:
- 移除
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.")
}
- 在
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
時,提供預設值。