Kotlin 實戰範例 (4) 基礎 (條件控制、循環執行)

Kotlin 實戰範例

條件控制可以讓程式執行或跳過某個區塊的程式碼,另外可以透過程式迴圈來指示電腦循環執行一段程式碼。

4.1 條件控制

4.1.1 if

if 敍述 (if-statement) 就是條件選擇,當條件符合時才執行區塊中的程式,還可以加上 else 來表示當條件不符合時要執行的區塊。

傳統的 if 敍述語法和大多數程式語言一樣,請看範例:

// 情境一
var max = a
if (b > a) {
  max = b
}

// 情境二
var max: Int
if (a > b) {
  max = a
} else {
  max = b
}

在這種情況下使用 if 敍述其實很沒效率,Kotlin 提供 if 表達式 (if-expressions) 語法,可以讓我們一行就完成上面的例子:

val max = if (a > b) a else b

敍述 (statement) 和表達式 (expression) 的差異在於,敍述是一個獨立的程式碼區塊,而表達式可以被當成值指派給變數或從函式中回傳,也就是說,表達式可以放在指派符號 = 的右邊,而敍述不行。

在可以使用 if 表達式的情境使用它,可以使程式碼變得簡潔及嚴謹。以此例來說,原本使用 var 宣告現在改成 val,這樣可以大大減少後續程式碼不小心改到變數的值的機會,而且符合優先使用 val 的準則;再來,程式碼只有一行,寫的少犯錯的機會就減少,閱讀上可以明確得知這段條件判斷的目的,較不容易造成誤解或想半天才知道在做什麼。

讀者可能有發現,if 表達式的語法很像 Java 中的三元運算子 (a > b) ? a : b,但是 Kotlin 沒有三元運算子,我們不需要額外多記一種語法,使用 if 表達式即可。if 表達式不只可以用在變數中,還可以用在函式,看看以下例子:

// 一般函式的寫法
fun max(a: Int, b: Int): Int {
  if (a > b) {
    return a
  } else {
    return b
  }
}

// 現在我們知道怎麼用 if 表達式了,可以改寫成
fun max(a: Int, b: Int): Int {
  return if (a > b) a else b
}

// 如果搭配函式表達式的話
fun max(a: Int, b: Int) = if (a > b) a else b

程式碼可以由 7 行縮短為 1 行,不僅寫起來、讀起來更快,程式碼的意圖也更加清楚。

如果 if 表達式內的程式碼不只一行,可以用大括號 {} 包起來,但是必須把結果值放在最後一行,而且不可以加 return,請看範例:

val max = if (a > b) {
  println("a 比較大")
  a // 結果值放在最後一行,不要加 return
} else {
  println("b 比較大")
  b // 結果值放在最後一行,不要加 return
}

另外,使用 if 表達式的時候,一定要有 else 區塊,因為無論如何一定要有值,才能指派給變數。

4.1.2 when

Kotlin 沒有 switch 這個關鍵字,而是設計了 when 敍述來取代它,請看範例:

val x: Any = 1
when (x) {
  1 -> print("x 是 1")
  2,3,4 -> print("x 可能是 2 或 3 或 4")
  in 5..10 -> print("x 在 5~10 的範圍內")
  is Int -> print("x 是整數")
  else -> {
    print("無法判斷 x")
  }
}

when 敍述可以使用括號 () 來接受要比對的參數,每個比對條件寫成一行,條件寫在前面,後接箭號 -> 表示符合時要如何處理。條件比對的靈活度很高,可以是單一值、多個值 (使用逗號分隔)、一段範圍 (使用 in 關鍵字) 等等,如果都不符合,會執行 else 條件的內容。遇到處理程式較多行時,可以用大括號 {} 包圍,如同範例中的 else 區塊。

我們不一定要使用括號 () 來接受參數,可以直接寫在比對條件中,前例可以改寫如下:

val x: Any = 1
when {
  x == 1 -> print("x 是 1")
  x == 2 || x == 3 || x == 4 -> print("x 可能是 2 或 3 或 4")
  x in 5..10 -> print("x 在 5~10 的範圍內")
  x is Int -> print("x 是整數")
  else -> {
    print("無法判斷 x")
  }
}

這裡只是舉例,建議使用前一種寫法較佳。

有時候當 if-else 條件過多時,建議改用 when 較佳,例如:

// 避免這種寫法
if (a == 0) {
  // ...
} else if (a < 100) {
  // ...
} else if (a in 100..200) {
  // ...
} else {
  // ...
}

// 改用 when 來增加易讀性
when {
  a == 0 -> // ...
  a < 100 -> // ...
  a in 100..200 -> // ...
  else -> // ...
}

如果所有可能條件都列出時,可以省略 else

when 一樣可以當成表達式,放在 = 的右邊,請看範例:

val x: Any = 100
val msg = when (x) {
  1 -> "x 是 1"
  2,3,4 -> "x 可能是 2 或 3 或 4"
  in 5..10 -> "x 在 5~10 的範圍內"
  is Int -> "x 是整數"
  else -> {
    // 這裡可以寫很多東西,但是記得要把結果值放在最後一行
    println("...")
    "無法判斷 x"
  }
}
println(msg) // x 是整數

記住,表達式最後一定會有一個結果值。如果使用大括號 {},記得要把結果值放在最後一行,而且不要加 return

寫過 Java 的讀者可能有發現,以上的例子竟然沒有 break 關鍵字,沒錯!when 的條件判斷並不會像 Java 的 switch 敍述一樣自動向下執行,所以不必使用 break 來終止。

註:Java 13 將有新版本的 switch 表達式 預覽版,用起來很像 when

4.2 條件控制

4.2.1 for

Kotlin 的 for 迴圈以範圍來決定循環次數,或以疊代的方式遍歷陣列或集合中的每個項目。Kotlin 沒有 Java 這種從 C 語言一直延續到現在的傳統 for 迴圈,請看範例:

/* Java */
for (int i = 0; i < 10; i++) {
  System.out.print(i);
}

這種迴圈讀起來不直覺,寫起來也麻煩。在 Kotlin 中可以使用 for in 迴圈,以雙點 .. 來指定範圍,看看以下的例子:

for (i in 0..9) {
  print(i)
}
// 或
for (i in 0.rangeTo(9)) {
  print(i)
}
// 或
for (i in 0 until 10) { // <== 不包含 10
  print(i)
}
// [結果] 0123456789

Kotlin 的 for in 迴圈和傳統 for 迴圈的行為是一樣的,但是語意較清楚、也較易閱讀。其中 in 關鍵字會將變數 i 的值限制在指定範圍內,並在每次循環時取得一個累進 1 的值。

設定範圍的方式有兩種,如果要將頭尾值都包含在內用 .. ,如果不包含尾端的值,像是前面 Java 的例子 i < 10 一樣,就用 until ;簡單地說就是 .. (兩個點) 是「從頭到尾」,until 則是「神龍見首不見尾」。

如果循環順序要反過來,也就是從大到小,可以使用 downTo

for (i in 9 downTo 0) {
  print(i)
}
// 或
for (i in 9.downTo(0)) {
  print(i)
}
// [結果] 9876543210

預設每次循環是累進 1,即跳 1 階,如果要一次跳 N 階,可以使用 step 來指定:

for (i in 0..9 step 3) {
  print(i)
}
// 或
for (i in 0.rangeTo(9).step(3)) {
  print(i)
}
// [結果] 0369

我們也可以使用 for in 迴圈來疊代陣列或集合,請看範例:

val names = listOf("Tony", "Tom", "Tiffany")
for (name in names) {
  println(name)
}
/* --- 結果 ---
Tony
Tom
Tiffany
*/

傳統 for 迴圈對陣列或集合循環時,可以依索引值來取得項目。在使用 for in 迴圈時如果需要索引值,可以使用 withIndex() 方法,請看範例:

val names = listOf("Tony", "Tom", "Tiffany")
for ((index, name) in names.withIndex()) {
  println("$index: $name")
}
/* --- 結果 ---
0: Tony
1: Tom
2: Tiffany
*/

Kotlin 還有一個方便的屬性叫做 indices,它會回傳陣列或集合的範圍,這樣我們就可以省去自己指定範圍的動作,前面的例子可以改寫如下:

for (index in names.indices) {
  println("$index: ${names[index]}")
}

以此例來說,indices 會回傳一段範圍值 0..2。善用 indices 可以避免存取索引值時超過陣列或集合範圍的例外。

4.2.2 while

以條件來決定循環次數的迴圈有兩種型式,一種是先判斷條件是否符合才執行的 while 迴圈,另一種 do while 則是先執行才判斷條件是否符合。先來看看 while 迴圈:

var i = 5
while (i > 0) {
  println(i)
  i--
}
// 54321

當條件成立,while 迴圈就會執行區塊中的程式。使用 while 迴圈要小心在沒有離開條件或離開條件無法達成的情況下,會形成無窮迴圈,以上例而言,只要把 i-- 拿掉,就形成無窮迴圈,因為 i 將永遠大於 0,條件永遠成立。

另一種 do while 迴圈會先執行區塊中的程式才去檢查條件:

var i = 1
do {
  println(i)
  i--
} while (i > 0)
// 1

這種迴圈的特性是,無論如何都至少會執行一次區塊中的程式。

4.2.3 離開迴圈

在實務上,我們可能會使用迴圈在集合中尋找資料,一旦找到符合的資料,就沒必要再繼續執行迴圈,這時候就需要有中途離開迴圈的方法。

有 3 個關鍵字可以離開迴圈:

  • 返回 return:跳離迴圈所在的函式,可以同時回傳值。
  • 中斷 break:離開目前的迴圈。
  • 繼續下一回 continue:中止目前這一輪迴圈的執行,直接跳到迴圈的開頭,執行下一輪。
return

return 會跳離迴圈最接近的函式。不管是否為巢狀迴圈 (即迴圈中還有另一個迴圈),永遠會跳離最接近的函式,請看範例:

fun demo() {
  for (i in 0..1) {
    println("for(i) 開始: $i")
    for (j in 0..3) {
      println("for(j) 開始: $j")
      if (j == 2) {
        println(">>> 離開 demo()")
        return
      }
      println("for(j) 結束: $j")
    }
    println("for(i) 結束: $i")
  }
  println("函式結束")
}
/* --- 結果 ---
for(i) 開始: 0
for(j) 開始: 0
for(j) 結束: 0
for(j) 開始: 1
for(j) 結束: 1
for(j) 開始: 2 
>>> 離開 demo() 
*/

我們可以看到,當 return 一被執行,就直接離開 demo() 函式了。

接下來,看看巢狀函式的例子:

fun outer() {
  println("outer() 開始")
  fun inner() {
    println("inner() 開始”)
    for (i in 0..3) {
      println("for(i) 開始: $i")
      if (i == 2) {
        println(">>> 離開 inner()")
        return
      }
      println("for(i) 結束: $i")
    }
    println("inner() 結束")
  }
  inner()
  println("outer() 結束")
}
/* --- 結果 ---
outer() 開始
inner() 開始
for(i) 開始: 0
for(i) 結束: 0
for(i) 開始: 1
for(i) 結束: 1
for(i) 開始: 2
>>> 離開 inner()
outer() 結束
*/

我們可以看到,return 離開了 inner() 後還繼續執行 outer() 中的程式。

break

中斷並離開目前的迴圈,請看範例:

for (i in 0..2) {
  for (j in 0..2) {
    println("($i, $j)")
    if (j == 0) {
      break
    }
  }
}/* --- 結果 ---
(0, 0)
(1, 0)
(2, 0)
*/

break 只會中斷並離開靠近它的那個迴圈,以巢狀迴圈來舉例就能看得出來。但是某些情況下,我們想要離開外面那個迴圈,該怎麼做呢?Kotlin 針對迴圈提供了標籤,標籤以 標籤名稱@ 來表示,我們就可以使用 break@標籤名稱 來指定要跳離的迴圈,請看範例:

loop@ for (i in 0..2) {
  for (j in 0..2) {
    println("($i, $j)")
    if (j == 0) {
      break@loop
    }
  }
}
/* --- 結果 ---
(0, 0)
*/

和前例相同,只是這次我們直接跳離外部迴圈。

continue

break 類似,只不過它不會離開迴圈,而是直接進行下一回的循環,請看範例:

for (i in 0..2) {
  if (i == 1) {
    continue
  }
  println(i)
}
/* --- 結果 ---
0
2
*/

1 被跳過了。continue 一樣可以搭配標籤來使用,指定跳到某個標籤的迴圈進行下一回的循環。

repeat

如果只是單純想要重覆執行某段程式碼一定的次數,可以使用 repeat,請看範例:

fun main() {
  repeat(3) {
    println("Hello")
  }
}
/*
Hello
Hello
Hello
*/


繼續閱讀:Kotlin 實戰範例 (5) 基礎 (函式、套件)

完整內容可以參考電子書:Google Play Pubu 樂天 Kobo



本文網址:https://blog.tonycube.com/2020/08/kotlin-by-example-4-control-flow-and-loop.html
Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀

我要留言

留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。