Kotlin 實戰範例 (5) 基礎 (函式、套件)

Kotlin 實戰範例

函式可以讓我們建立一個程式碼區塊來執行特定的任務,函式可以有輸入參數及回傳值,也可以都沒有,一個函式應該只做一件事。

5.1 建立函式

在 Java 中,所有函式都無法單獨存在,它只能被放在類別中,並被稱呼為方法。但是 Kotlin 不同,在 Kotlin 中,函式被提升為一等公民,位階和類別相等,因此可以單獨存在。

要宣告一個函式必須使用關鍵字 fun,這是取 function 的前 3 個字母,而不是單字 fun。函式可以有輸入值,稱為參數,及處理後的結果,稱為回傳值,以下為幾個範例:

// 無參數、無回傳值的函式
fun foo() {
  // ...
}
// 函式一定會有回傳值,如果沒有指定回傳值,預設會回傳「空值 Unit」
// Unit 會省略不寫,所以上面的例子實際上是這樣
fun foo(): Unit {
  // ...
}
// 有參數、有回傳值的函式
fun sum(a: Int, b: Int): Int {
  return a + b
}
// 使用函式表達式
fun sum(a: Int, b: Int) = a + b

參數是以 名稱: 資料型別 這樣的方式來宣告,多個參數以逗號分隔。回傳值則是在結尾括號後方以 : 資料型別 的方式宣告。

如果有回傳值,必須指定其資料型別,無回傳值則回傳 UnitUnit 型別只有一個值,即 Unit 物件,它相當於 Java 的 void。Kotlin 官方建議,如果函式的回傳值是 Unit,應該省略不寫。

5.2 參數的預設值

在宣告參數時,可以同時使用指派符號 = 來指定它的預設值。當呼叫函式時,如果沒有輸入該參數的值,在函式內就會直接使用預設值,請看範例:

fun sum(a: Int = 0, b: Int = 0) {
  print(a + b)
}
sum(1, 2) // [結果] 3
sum(1) // [結果] 1
sum() // [結果] 0

Java 沒有參數預設值的功能,而是以函式多載 (或稱方法多載,overloading) 的方式來實作,以此例來說,在 Java 裡就必須實作 3 個同名方法才能做到一樣的事。

5.3 以名稱指定參數的值

我們在呼叫函式時,必須依序輸入參數的值,但是如果某個參數有設定預設值而我們想省略它,就會出現不明確的情況,到底輸入的值是要給哪個參數。這時候我們可以明確指定參數的名稱,使用這種方式給值還有個好處,我們就不需要依序來輸入參數的值,任意順序都可以,請看範例:

// 參數 b 有指定預設值
fun sum(a: Int, b: Int = 10, c: Int) {
  println(a + b + c)
}
// 所有參數必須依序指定值
sum(1, 2, 3)
// 使用參數名稱指定值,略過有預設值的 b
sum(a = 1, c = 3)
// 只要以名稱指定參數的值,就可以忽略原始參數的順序
sum(c = 1, a = 2, b = 3)

5.4 數量不固定的參數 (Varargs)

在某些情況下,參數的數量可能不固定,雖然可以使用陣列來解決,但是使用上就沒那麼靈活,使用數量不固定的參數會更加方便。我們只要在參數名稱前加上 vararg 關鍵字,代表此參數的數量是不固定的,在函式內部會以陣列來表示這個參數值,請看範例:

fun sum(vararg numbers: Int) {
  var total = 0
  for (i in numbers.indices) {
    total += numbers[i]
  }
  println(total)
}
sum() // [結果] 0
sum(1, 2) // [結果] 3
sum(1, 2, 3, 4, 5) // [結果] 15

如果剛好我們有一個陣列,反而會無法指定給 vararg 參數。Kotlin 已經想到這個問題,解法很簡單,使用展開 (spread) 運算子即可。展開運算子是在陣列前面加上星號 *,陣列中的項目就會自動展開為一個一個的值,請看範例:

val numbers = intArrayOf(2, 4, 6, 8, 10)
sum(*numbers) // [結果] 30

一個函式只能有一個 vararg 參數。

注意!如果一個函式有多個參數,而其中一個是 vararg 參數,最好把它放在最後面,否則很容易出錯。以下示範可能遇到的錯誤,請避免:

// 不佳的寫法
fun sum(vararg numbers: Int, a: String, b: Int) {
  // ...
}
// 會發生錯誤的參數值
sum(numbers = 1,2,3, a = "aaa", b = 1)
// 解決方法
sum(numbers = *intArrayOf(1,2,3), a = "aaa", b = 1)

// 建議的寫法:將 vararg 參數移到最後面
fun sum(a: String, b: Int, vararg numbers: Int) {
  // ...
}
// 多出來的參數值都會分配給 numbers
sum("aaa", 1, 1, 2, 3)

5.5 函式表達式

我們知道表達式 (expression) 可以當成值被指派或回傳,也就是可以放在指派符號 = 的右邊,函式也有表達式的形式,使用方法是將大括號省略,直接以 = 來指定回傳值,請看範例:

// 函式敍述
fun plus(a: Int, b: Int): Int {
  return a + b
}
// 函式表達式
fun plus(a: Int, b: Int): Int = a + b

5.6 中綴表示法 (Infix notation)

Kotlin 提供 infix 這個關鍵字,當我們加在函式宣告前面時,此函式可以被當成中綴符號來使用,替代原本的 .() 呼叫方式,請看範例 (這裡有用到擴充函式,後面的章節會在說明):

// 宣告
infix fun Int.plus(x: Int) = this + x

// 呼叫方法
println(1.plus(2))

// 中綴表示法
println(1 plus 2)

註:Kotlin 已內建 plus() 方法,這裡僅做示範。

infix 函式有幾個要求:

  • 必須是成員函式 (即類別的方法) 或擴充函式 (extension function)
  • 必須有、且只有一個參數
  • 參數不能是數量不固定的 vararg 參數
  • 參數不能有預設值

5.7 函式的範圍

5.7.1 區域函式

Kotlin 將函式提升為一級函式,有別於 Java 只能在類別中宣告函式 (方法),Kotlin 可以將函式宣告在檔案的最上層,和類別同一位階,不必依賴於類別,而且可以在函式中宣告另一個函式,此內部函式就是區域函式。

區域函式的範例:

fun plus(a: Int, b: Int): Int {
  val result = a + b
  // 區域函式
  fun showResult() {
    // 區域函式可以存取外部函式的變數
    println(result)
  }
  // 呼叫區域函式
  showResult()
  return result
}

當內部函式參照了外部函式的變數時,就形成了 閉包 (closure)。閉包是用來將一個函式與一組「私有」變數之間建立關係,這裡的「私有」變數是指外部函式所屬的變數,由於無法從外界存取,形同此外部函式的「私有」變數,當一個區域 (內部) 函式使用了這個「私有」變數時,它們就建立了連結關係,這整個連結關係就稱為閉包。

5.7.2 成員函式

成員 (member) 是一個術語,是對類別裡的變數或函式的一個統稱,意即它們都是這個類別的成員。成員函式又可稱為方法。

成員函式的範例:

class Calculator() {
  // 成員函式 (方法)
  fun plus(a: Int, b: Int) = a + b
}

// 實體化類別成為物件並呼叫其方法
Calculator().plus(1, 2)

註:給 Java 開發者,Kotlin 在實體化類別 (建立物件) 時不需要使用 new 關鍵字。

5.8 套件

套件可以用來組織一組相關的程式碼,例如類別、介面或函式等,並且可以避免名稱衝突,它們的完整名稱就是「套件名稱 + 類別、介面或函式的名稱」。在沒有使用套件的情況下,會使用預設的套件名稱,也就是沒有套件名稱。

套件名稱使用 package 關鍵字來設定,並且必須寫在檔案的最上方,請看範例:

package com.tonycube.app

class Hello

Java 開發者在這裡必須要注意的一點是,Kotlin 的 package 名稱是虛擬的;在 Java 中,我們必須為 package 的區段建立相對應的目錄結構,在 Kotlin 中不是必要的。例如:

[Java]
package com.tonycube.app;
[目錄結構]
src/
  main/
    java/
      com/
        tonycbe/
          app/
            Hello.java
[類別的完整名稱]
com.tonycube.app.Hello

-----------------------
[Kotlin]
package com.tonycube.app
[目錄結構]
src/
  main/
    kotlin/
      Hello.kt
[類別的完整名稱]
com.tonycube.app.HelloKt

5.9 匯入套件

import 關鍵字可以讓我們指定要使用哪些套件,它的位置在 package 關鍵字之後,在所有程式碼之前。匯入套件有幾種寫法:

  • 全部匯入:指定匯入套件之中所有的類別、介面及函式等,使用 * 表示
  • 指名匯入:指定要匯入的類別、介面及函式等的名稱
  • 指名匯入並使用匿名:指定要匯入的類別、介面及函式等的名稱,並改以另一個名稱來使用

請看範例:

import aaa.bbb.ccc.* // 匯入該套件全部的內容
import aaa.bbb.ccc.Hello // 指定匯入 Hello 類別
import aaa.bbb.ccc.Hello as yo // 指定匯入 Hello 類別並重新命名為 yo

不管有沒有匯入任何套件,每一個 Kotlin 檔案預設都會匯入以下套件:

另外,會依編譯的目標平台,額外自動匯入:

JVM:

JS:

Kotlin 沒有像 Java 中的 import static 匯入方式,一律使用 import 即可。指定為 private 的類別或函式無法被匯入。


繼續閱讀:Kotlin 實戰範例 (6) 類別與物件

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



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

我要留言

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