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() {
}
- 建立可為空值
Int
類型的number
變數,然後為該變數指派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 會假設變數不可為空值。因此,您可以將該變數視為是不可為空值變數一般,在這個主體中自由存取變數的方法或屬性,無需使用?.
安全呼叫運算子或!!
非空值斷言運算子。else
分支版本內的主體 2 會假設變數為null
。因此,當變數為null
時,您就能在這個主體中新增應當執行的陳述式。else
分支版本為選用項目。當null
檢查失敗時,您只能使用if
條件式來執行null
檢查,且不提供預設動作。
如果有多行程式碼使用可為空值變數,那麼將 null
檢查與 if
條件式搭配使用較為簡便。相反地,?.
安全呼叫運算子則適合用於可為空值變數的單一參照。
如要為 favoriteActor
變數編寫含有 null
檢查的 if/else
陳述式,請按照下列步驟操作:
- 將
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 會假設變數不可為空值。因此,您可以將該變數視為是不可為空值變數一般,在這個主體中存取變數的方法或屬性,無需使用?.
安全運算子或!!
非空值斷言運算子。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() {
val favoriteActor: String? = "Sandra Oh"
val lengthOfName = if(favoriteActor != null) {
favoriteActor.length
} else {
}
}
favoriteActor
變數的 length
屬性可直接使用 .
運算子存取。
- 在
else
分支版本的主體中新增0
值:
fun main() {
val 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() {
val 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() {
val 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() {
val 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
指派給可為空值變數。 - 如要存取可為空值變數的方法或屬性,必須使用
?.
安全呼叫運算子或!!
非空值斷言運算子。 - 可使用含有
null
檢查的if/else
陳述式,在不可為空值項目中存取可為空值變數。 - 可使用
if/else
運算式將可為空值變數轉換成不可為空值類型。 - 當可為空值變數為
null
時,可使用if/else
運算式或?:
Elvis 運算子提供該變數的預設值。