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
參數是以 名稱: 資料型別
這樣的方式來宣告,多個參數以逗號分隔。回傳值則是在結尾括號後方以 : 資料型別
的方式宣告。
如果有回傳值,必須指定其資料型別,無回傳值則回傳 Unit
。Unit
型別只有一個值,即 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 檔案預設都會匯入以下套件:
- kotlin.*
- kotlin.annotation.*
- kotlin.collections.*
- kotlin.comparisons.*
- kotlin.io.*
- kotlin.ranges.*
- kotlin.sequences.*
- kotlin.text.*
另外,會依編譯的目標平台,額外自動匯入:
JVM:
- java.lang.*
- kotlin.jvm.*
JS:
Kotlin 沒有像 Java 中的 import static
匯入方式,一律使用 import
即可。指定為 private
的類別或函式無法被匯入。
繼續閱讀:Kotlin 實戰範例 (6) 類別與物件
完整內容可以參考電子書:Google Play Pubu 樂天 Kobo
由 Tony Blog 撰寫,請勿全文複製,轉載時請註明出處及連結,謝謝 😀
我要留言
留言小提醒:
1.回覆時間通常在晚上,如果太忙可能要等幾天。
2.請先瀏覽一下其他人的留言,也許有人問過同樣的問題。
3.程式碼請先將它編碼後再貼上。(線上編碼:http://bit.ly/1DL6yog)
4.文字請加上標點符號及斷行,難以閱讀者恕難回覆。
5.感謝您的留言,您的問題也可能幫助到其他有相同問題的人。