1. 程式人生 > >Kotlin函式與Lambdas(一)--- 函式

Kotlin函式與Lambdas(一)--- 函式

1、函式宣告

Kotlin 中的函式使用 fun 關鍵字宣告:

fun double(x: Int): Int {
    return 2*x
}

2、函式用法

呼叫函式使用傳統的方法:

val result = double(2)

呼叫 成員函式 使用 點 表示法:

Sample().foo() // 建立類 Sample 例項並呼叫 foo

2.1 引數

函式引數使用 Pascal 表示法定義,即 name: type。引數用逗號隔開。每個引數必須有顯式型別:

fun powerOf(number: Int, exponent: Int) {
……
}

2.2 預設引數

函式引數可以有預設值,當省略相應的引數時使用預設值。與其他語言相比,這可以減少過載數量:

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
……
}

預設值通過型別後面的 = 及給出的值來定義。

覆蓋方法總是使用與 基類方法相同的預設引數值。 當覆蓋一個帶有預設引數值的方法時,必須從簽名中省略預設引數值:

open class A {
    open fun foo(i: Int = 10) { …… }
}

class B : A() {
    override fun
foo(i: Int) { …… } // 不能有預設值 }

如果一個預設引數在一個無預設值的引數之前,那麼該預設值只能通過使用命名引數呼叫該函式來使用:

fun foo(bar: Int = 0, baz: Int) { /* …… */ }

foo(baz = 1) // 使用預設值 bar = 0

不過如果最後一個 lambda 表示式引數從括號外傳給 函式呼叫,那麼允許預設引數不傳值:

fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* …… */ }

foo(1) { println("hello") } // 使用預設值 baz
= 1 foo { println("hello") } // 使用兩個預設值 bar = 0 與 baz = 1

2.3 命名引數

可以在呼叫函式時使用命名的函式引數。當一個函式有大量的引數或預設引數時這會非常方便。

給定以下函式:

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') {
……
}

我們可以使用預設引數來呼叫它:

reformat(str)

然而,當使用非預設引數呼叫它時,該呼叫看起來就像:

reformat(str, true, true, false, '_')

使用命名引數我們可以使程式碼更具有可讀性:

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

並且如果我們不需要所有的引數:

reformat(str, wordSeparator = '_')

當一個函式呼叫混用位置引數與命名引數時,所有位置引數都要放在第一個命名引數之前。例如,允許呼叫 f(1, y = 2) 但不允許 f(x = 1, 2)

可以通過使用星號操作符將可變數量引數(vararg) 以命名形式傳入:

fun foo(vararg strings: String) { /* …… */ }

foo(strings = *arrayOf("a", "b", "c"))
foo(strings = "a") // 對於單個值不需要星號

請注意,在呼叫 Java 函式時不能使用命名引數語法,因為 Java 位元組碼並不總是保留函式引數的名稱。

2.4 返回 Unit 的函式

如果一個函式不返回任何有用的值,它的返回型別是 Unit。Unit 是一種只有一個值——Unit 的型別。這個值不需要顯式返回:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello ${name}")
    else
        println("Hi there!")
    // `return Unit` 或者 `return` 是可選的
}

Unit 返回型別宣告也是可選的。上面的程式碼等同於:

fun printHello(name: String?) {
    ……
}

2.4 單表示式函式

當函式返回單個表示式時,可以省略花括號並且在 = 符號之後指定程式碼體即可:

fun double(x: Int): Int = x * 2

當返回值型別可由編譯器推斷時,顯式宣告返回型別是可選的:

fun double(x: Int) = x * 2

2.5 顯式返回型別

具有塊程式碼體的函式必須始終顯式指定返回型別,除非他們旨在返回 Unit,在這種情況下它是可選的。 Kotlin 不推斷具有塊程式碼體的函式的返回型別,因為這樣的函式在程式碼體中可能有複雜的控制流,並且返回型別對於讀者(有時甚至對於編譯器)是不明顯的。

2.6 可變數量的引數(Varargs)

函式的引數(通常是最後一個)可以用 vararg 修飾符標記:

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

允許將可變數量的引數傳遞給函式:

val list = asList(1, 2, 3)

在函式內部,型別 T 的 vararg 引數的可見方式是作為 T 陣列,即上例中的 ts 變數具有型別 Array <out T>

只有一個引數可以標註為 vararg。如果 vararg 引數不是列表中的最後一個引數, 可以使用命名引數語法傳遞其後的引數的值,或者,如果引數具有函式型別,則通過在括號外部傳一個 lambda

當我們呼叫 vararg-函式時,我們可以一個接一個地傳參,例如 asList(1, 2, 3),或者,如果我們已經有一個數組並希望將其內容傳給該函式,我們使用伸展(spread)操作符(在陣列前面加 *):

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

2.7 中綴表示法

函式還可以用中綴表示法呼叫,當

  • 他們是成員函式或擴充套件函式;
  • 他們只有一個引數;
  • 他們用 infix 關鍵字標註。
// 給 Int 定義擴充套件
infix fun Int.shl(x: Int): Int {
……
}


// 用中綴表示法呼叫擴充套件函式

1 shl 2

// 等同於這樣

1.shl(2)

3 、函式作用域

在 Kotlin 中函式可以在檔案頂層宣告,這意味著你不需要像一些語言如 Java、C# 或 Scala 那樣建立一個類來儲存一個函式。此外除了頂層函式,Kotlin 中函式也可以宣告在區域性作用域、作為成員函式以及擴充套件函式。

3.1 區域性函式

Kotlin 支援區域性函式,即一個函式在另一個函式內部:

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: Set<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

區域性函式可以訪問外部函式(即閉包)的區域性變數,所以在上例中,visited 可以是區域性變數:

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

3.2 成員函式

成員函式是在類或物件內部定義的函式:

class Sample() {
    fun foo() { print("Foo") }
}

成員函式以點表示法呼叫:

Sample().foo() // 建立類 Sample 例項並呼叫 foo

關於類和覆蓋成員的更多資訊參見類和繼承。

4、 泛型函式

函式可以有泛型引數,通過在函式名前使用尖括號指定:

fun <T> singletonList(item: T): List<T> {
    // ……
}

關於泛型函式的更多資訊參見泛型。

5、行內函數

行內函數 下一章節講

6、擴充套件函式

7、高階函式和 Lambda 表示式

高階函式和 Lambda 表示式在其自有章節講述。

8、尾遞迴函式

Kotlin 支援一種稱為尾遞迴的函數語言程式設計風格。 這允許一些通常用迴圈寫的演算法改用遞迴函式來寫,而無堆疊溢位的風險。 當一個函式用 tailrec 修飾符標記並滿足所需的形式時,編譯器會優化該遞迴,留下一個快速而高效的基於迴圈的版本:

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

這段程式碼計算餘弦的不動點(fixpoint of cosine),這是一個數學常數。 它只是重複地從 1.0 開始呼叫 Math.cos,直到結果不再改變,產生0.7390851332151607的結果。最終程式碼相當於這種更傳統風格的程式碼:

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (x == y) return y
        x = y
    }
}

要符合 tailrec 修飾符的條件的話,函式必須將其自身呼叫作為它執行的最後一個操作。在遞迴呼叫後有更多程式碼時,不能使用尾遞迴,並且不能用在 try/catch/finally 塊中。目前尾部遞迴只在 JVM 後端中支援。