一開賽就有說到新時代語言 Kotlin 據有變數、資料型態與 Null Safety,今天要進一步控制程式的執行方向。我們將學習 if、when、for、while、do-while、break、continue,最後使用 try-catch-throw 處理錯誤。參考資料:https://kotlinlang.org/docs/control-flow.html
if 條件判斷when 多條件判斷for、while、do-while 迴圈step 間隔走訪break 與 continue
try-catch-finally 例外處理throw 主動拋出例外toIntOrNull() 與 Elvis 運算子 ?:
參考資料:https://kotlinlang.org/docs/control-flow.html
if 是最基本的條件判斷。當條件成立時執行第一個區塊,不成立時則執行 else 區塊。Kotlin 的 if 同時也是運算式,因此可以直接產生一個結果並指派給變數。參考資料:https://kotlinlang.org/docs/control-flow.html#if-expression
fun main() {
print("請輸入成績:")
// 使用 toIntOrNull() 安全轉換成整數。
// 如果輸入無法轉換,使用 Elvis 運算子 ?: 給予預設值 0。
val score = readln().toIntOrNull() ?: 0
val result = if (score >= 60) {
"及格"
} else {
"不及格"
}
println("成績:$score")
println("結果:$result")
}
輸入 80 時,執行結果如下:
請輸入成績:80
成績:80
結果:及格
如果需要判斷很多種情況,連續寫很多組 if-else if 會降低可讀性。Kotlin 提供 when,可以依照數值、字串、範圍或其他條件選擇要執行的分支。參考資料:https://kotlinlang.org/docs/control-flow.html#when-expressions-and-statements
fun main() {
print("請輸入 1~11 的整數:")
val value = readln().toIntOrNull() ?: 0
when (value) {
1 -> println("A")
2 -> println("B")
// 同一個分支可以放入多個值。
3, 4 -> println("E")
// in 可以判斷數值是否位於指定範圍。
in 6..10 -> println("F~J")
11 -> println("K")
// 前面的條件皆不成立時執行 else。
else -> println("輸入不在指定範圍內")
}
}
when 不只可以執行程式,也能直接回傳結果。將判斷結果指派給變數,可以減少重複的 println()。參考資料:https://kotlinlang.org/docs/control-flow.html#when-expressions-and-statements
fun main() {
print("請輸入成績:")
val score = readln().toIntOrNull() ?: -1
val scoreStatus = when (score) {
in 0..59 -> "不及格"
in 60..100 -> "及格"
else -> "輸入錯誤"
}
println("成績狀態:$scoreStatus")
}
字串也可以放進 when。使用 lowercase() 將輸入統一轉成小寫,可避免使用者輸入 Apple 或 APPLE 時比對失敗。參考資料:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/lowercase.html
fun main() {
print("請輸入水果英文名稱:")
val fruitName = readln()
val price = when (fruitName.lowercase()) {
"apple" -> 100
"pear" -> 150
"pineapple" -> 80
else -> 50
}
println("水果:$fruitName")
println("價格:$$price")
}
注意 Kotlin 字串中的 $ 代表字串模板開頭。如果要輸出貨幣符號 $,可以寫成 \$;若 $ 後接的是變數,也可以利用字串模板組合輸出。參考資料:https://kotlinlang.org/docs/strings.html#string-templates
when 後面也可以不放變數,直接在每個分支撰寫布林條件。這種寫法很適合處理分數、生命值、溫度及年齡區間。參考資料:https://kotlinlang.org/docs/control-flow.html#when-expressions-and-statements
fun main() {
print("請輸入生命值:")
val healthPoint = readln().toIntOrNull() ?: 0
val isBlessed = true
val state = when {
healthPoint == 100 -> {
"狀態良好"
}
healthPoint >= 90 -> {
"只有一些擦傷"
}
healthPoint >= 75 -> {
if (isBlessed) {
"受到祝福保護"
} else {
"受到輕傷"
}
}
healthPoint >= 15 -> {
"傷勢嚴重"
}
else -> {
"生命垂危"
}
}
println("生命狀態:$state")
}
when 會由上往下檢查條件,找到第一個成立的分支後便停止,所以範圍判斷通常應從較大的數值開始排列。參考資料:https://kotlinlang.org/docs/control-flow.html#when-expressions-and-statements
當程式需要重複執行某件事時,可以使用迴圈。for 適合走訪數值範圍、字串、陣列或集合。參考資料:https://kotlinlang.org/docs/control-flow.html#for-loops
fun main() {
for (number in 1..10) {
println(number)
}
}
1..10 代表包含 1 和 10 的封閉區間,因此程式會依序印出 1~10。參考資料:https://kotlinlang.org/docs/ranges.html
step 可以設定每次遞增的距離。以下程式從 1 開始,每次增加 2,因此會取得 1、3、5、7、9。參考資料:https://kotlinlang.org/docs/ranges.html#progression
fun main() {
val numberRange = 1..10
var sum = 0
// 從 1 開始,每次增加 2,只會取得奇數。
for (number in numberRange step 2) {
sum += number
}
println("1 到 10 的奇數總和:$sum")
}
執行結果:
1 到 10 的奇數總和:25
巢狀迴圈就是在一個迴圈裡再放入另一個迴圈。外層控制被乘數,內層控制乘數,即可產生完整的九九乘法表。參考資料:https://kotlinlang.org/docs/control-flow.html#for-loops
fun main() {
printMultiplicationTable()
}
/**
* 印出完整的九九乘法表。
*/
fun printMultiplicationTable() {
// 外層迴圈控制第一個數字。
for (i in 1..9) {
// 內層迴圈控制第二個數字。
for (j in 1..9) {
println("$i × $j = ${i * j}")
}
}
}
部分執行結果:
1 × 1 = 1
1 × 2 = 2
1 × 3 = 3
1 × 4 = 4
1 × 5 = 5
1 × 6 = 6
1 × 7 = 7
1 × 8 = 8
1 × 9 = 9
2 × 1 = 2
2 × 2 = 4
2 × 3 = 6
while 會先判斷條件;條件成立才執行迴圈內容。如果條件一開始就不成立,迴圈可能一次也不會執行。參考資料:https://kotlinlang.org/docs/control-flow.html#while-loops
下面製作一個 1~10 的猜數字遊戲,玩家最多可以猜 5 次:
fun main() {
guessNumber()
}
/**
* 1~10 猜數字遊戲。
* 玩家最多可以猜 5 次。
*/
fun guessNumber() {
// random() 會從 1..10 中隨機取出一個整數。
val answer = (1..10).random()
var guess = -1
var attempt = 1
// 答案尚未猜中,而且次數不超過 5 次時繼續執行。
while (guess != answer && attempt <= 5) {
print("第 $attempt 次,請猜一個 1~10 的數字:")
// 如果輸入不是整數,先使用 -1 代替。
guess = readln().toIntOrNull() ?: -1
when {
guess !in 1..10 -> {
println("請輸入 1~10 之間的整數!")
}
guess < answer -> {
println("太小了!")
}
guess > answer -> {
println("太大了!")
}
else -> {
println("恭喜你猜對了!")
}
}
attempt++
}
// 迴圈結束後仍未猜中,公布答案。
if (guess != answer) {
println("挑戰失敗,正確答案是:$answer")
}
}
do-while 與 while 最大的差異是:do-while 會先執行一次程式,再檢查條件,所以無論條件是否成立,內容至少會執行一次。參考資料:https://kotlinlang.org/docs/control-flow.html#while-loops
以下使用 do-while 計算 1~10 的總和:
fun main() {
var value = 1
var sum = 0
do {
sum += value
value++
} while (value <= 10)
println("1 到 10 的總和:$sum")
}
執行結果:
1 到 10 的總和:55
break 和 continue 都可以改變迴圈的執行流程,但用途不同。break 會立刻結束整個迴圈;continue 則只跳過目前這一次,接著執行下一次迴圈。參考資料:https://kotlinlang.org/docs/returns.html#break-and-continue-labels
fun main() {
print("請輸入停止數字(1~10):")
val stopNumber = readln().toIntOrNull() ?: 0
var sum = 0
for (number in 1..10) {
if (number == stopNumber) {
println("遇到 $stopNumber,結束迴圈!")
break
}
sum += number
println("目前數字:$number,目前總和:$sum")
}
println("最後總和:$sum")
}
如果輸入 5,程式只會加總 1+2+3+4,遇到 5 時立即離開迴圈。參考資料:https://kotlinlang.org/docs/returns.html#break-and-continue-labels
fun main() {
print("請輸入要跳過的數字(1~10):")
val skipNumber = readln().toIntOrNull() ?: 0
var sum = 0
for (number in 1..10) {
if (number == skipNumber) {
println("跳過數字:$skipNumber")
continue
}
sum += number
println("目前數字:$number,目前總和:$sum")
}
println("最後總和:$sum")
}
如果輸入 5,程式會略過 5,但仍會繼續處理 6~10。參考資料:https://kotlinlang.org/docs/returns.html#break-and-continue-labels
使用 readln().toInt() 將文字轉成整數時,如果使用者輸入 ABC,程式便會產生 NumberFormatException 並中斷。可預期的錯誤應該適當處理,避免整個 App 因單一錯誤而停止。參考資料:https://kotlinlang.org/docs/exceptions.html
以下程式沒有例外處理:
fun main() {
print("請輸入一個整數:")
// 如果輸入 ABC,這一行會拋出 NumberFormatException。
val number = readln().toInt()
println("你輸入了:$number")
}
try 放置可能產生例外的程式;catch 負責接住並處理例外;finally 則無論有沒有發生例外都會執行,常用來關閉檔案、資料庫或網路連線等資源。參考資料:https://kotlinlang.org/docs/exceptions.html#handle-exceptions-using-try-catch-blocks
fun main() {
print("請輸入一個整數:")
try {
val number = readln().toInt()
println("你輸入了:$number")
} catch (e: NumberFormatException) {
// 只捕捉數字格式錯誤,比直接捕捉 Exception 更明確。
println("輸入錯誤,請輸入有效的整數!")
} finally {
// 不論成功或失敗都會執行。
println("程式執行結束。")
}
}
輸入正確整數:
請輸入一個整數:100
你輸入了:100
程式執行結束。
輸入非數字文字:
請輸入一個整數:ABC
輸入錯誤,請輸入有效的整數!
程式執行結束。
除了系統自動產生的例外,我們也可以使用 throw 主動拋出例外。例如名字不可空白,也不可包含數字或符號,只要資料不符合規則就中止正常流程並交給 catch 處理。參考資料:https://kotlinlang.org/docs/exceptions.html#throw-exceptions-with-the-throw-expression

發布到 iThome 前,請先將上面的流程圖上傳至 iThome 圖片空間,再換成平台產生的圖片網址。
完整程式如下:
fun main() {
print("請輸入你的名字:")
val name = readln()
try {
// readln() 回傳非空字串,因此這裡檢查是否為空字串即可。
if (name.isEmpty()) {
throw IllegalArgumentException("名字不能為空!")
}
// 逐一檢查名字中的每一個字元。
name.forEach { character ->
// 只要找到不是字母的字元,就主動拋出例外。
if (!character.isLetter()) {
throw IllegalArgumentException("名字只能包含字母!")
}
}
// 所有檢查皆通過才會執行。
println("你好,$name!")
} catch (e: IllegalArgumentException) {
// 顯示 throw 所設定的例外訊息。
println("錯誤:${e.message}")
}
}
這一版將原本的 Exception 改成 IllegalArgumentException,因為發生錯誤的原因是「傳入資料不符合要求」,使用更明確的例外型別有助於閱讀及維護程式。參考資料:https://kotlinlang.org/docs/exceptions.html#create-custom-exceptions
程式會依照下列順序執行:
try 區塊。throw 拋出例外。forEach 逐一檢查每個字元。catch 顯示錯誤原因。參考資料:https://kotlinlang.org/docs/exceptions.html
輸入正確名字:
請輸入你的名字:Alex
你好,Alex!
輸入含有數字的名字:
請輸入你的名字:Alex123
錯誤:名字只能包含字母!
直接按下 Enter:
請輸入你的名字:
錯誤:名字不能為空!
Kotlin 使用可為空型別來降低 NullPointerException 發生的機會。在型別後加上 ?,代表該變數允許儲存 null。參考資料:https://kotlinlang.org/docs/null-safety.html
fun main() {
val name: String? = null
// 安全呼叫運算子 ?.:
// name 為 null 時,不會呼叫 length,而是回傳 null。
val nullableLength: Int? = name?.length
println("nullableLength:$nullableLength")
// Elvis 運算子 ?::
// 左邊為 null 時,改用右邊的預設值 0。
val safeLength: Int = name?.length ?: 0
println("safeLength:$safeLength")
}
執行結果:
nullableLength:null
safeLength:0
!!!! 會強制將可為空型別視為不可為空。當變數實際上是 null 時,程式會拋出 NullPointerException,所以只有在完全確定資料不可能為空時才能使用。參考資料:https://kotlinlang.org/docs/null-safety.html#not-null-assertion-operator
fun main() {
val text: String? = null
try {
// text 是 null,使用 !! 會產生 NullPointerException。
val length = text!!.length
println("字串長度:$length")
} catch (e: NullPointerException) {
println("發生錯誤:字串目前是 null")
}
}
更安全的寫法是使用 ?. 搭配 ?::
fun main() {
val text: String? = null
// text 為 null 時,直接將長度設為 0。
val length = text?.length ?: 0
println("字串長度:$length")
}
toInt() 轉換失敗時會拋出 NumberFormatException;toIntOrNull() 轉換失敗時則回傳 null。如果只是接收使用者輸入,通常使用 toIntOrNull() 會更安全。參考資料:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/to-int-or-null.html
fun main() {
print("請輸入年齡:")
// 輸入無法轉成整數時得到 null,再使用 ?: 指定預設值。
val age = readln().toIntOrNull() ?: 0
println("你的年齡是:$age")
}
兩者適用情境如下:
| 寫法 | 轉換失敗結果 | 適合情境 |
|---|---|---|
text.toInt() |
拋出例外 | 格式必須正確,錯誤時需要進入 catch |
text.toIntOrNull() |
回傳 null |
使用者輸入、資料格式可能不正確 |
text.toIntOrNull() ?: 0 |
使用預設值 0 |
允許使用預設值繼續執行 |
參考資料:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/to-int-or-null.html
最後將今天學到的 while、when、範圍、continue、break、throw 與 try-catch 組合成完整的猜數字遊戲。參考資料:https://kotlinlang.org/docs/control-flow.html
fun main() {
val answer = (1..10).random()
val maximumAttempts = 5
var attempt = 1
var isCorrect = false
println("歡迎來到 Kotlin 猜數字遊戲!")
println("請猜一個 1~10 的整數,你共有 $maximumAttempts 次機會。")
while (attempt <= maximumAttempts) {
print("第 $attempt 次:")
try {
val input = readln()
// toIntOrNull() 轉換失敗時,主動拋出例外。
val guess = input.toIntOrNull()
?: throw IllegalArgumentException("請輸入整數!")
// 檢查輸入是否位於允許的範圍。
if (guess !in 1..10) {
throw IllegalArgumentException("數字必須介於 1~10!")
}
when {
guess < answer -> {
println("太小了!")
}
guess > answer -> {
println("太大了!")
}
else -> {
println("恭喜你猜對了,答案就是 $answer!")
isCorrect = true
// 猜中後立即離開 while 迴圈。
break
}
}
// 只有有效的猜測才計算次數。
attempt++
} catch (e: IllegalArgumentException) {
println("輸入錯誤:${e.message}")
// 錯誤輸入不計算次數,直接重新執行迴圈。
continue
}
}
if (!isCorrect) {
println("挑戰失敗,正確答案是 $answer。")
}
println("遊戲結束。")
}
今天我們開始讓 Kotlin 程式具備判斷、重複執行及處理錯誤的能力:
if 適合處理簡單的兩種結果。when 適合處理多個值或範圍。for 適合走訪範圍及集合。while 會先判斷條件再執行。do-while 至少會執行一次。break 會結束整個迴圈。continue 只會跳過目前這一次迴圈。try 放置可能出錯的程式。catch 負責接住並處理例外。finally 無論成功或失敗都會執行。throw 可以主動拋出例外。toIntOrNull() 能降低輸入格式錯誤造成的程式中斷。?. 與 ?: 通常比直接使用 !! 更安全。參考資料:https://kotlinlang.org/docs/control-flow.html
下一篇將進入 Kotlin 函式與 Lambda,介紹函式參數、回傳值、預設參數、命名參數、單一運算式函式、匿名函式,以及如何把 Lambda 當成參數傳給其他函式。參考資料:https://kotlinlang.org/docs/functions.html
toIntOrNull():https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/to-int-or-null.html