小獅子的Kotlin學習之路(十六)

函數類型與Lambda表達式

在Kotlin高階函數中,涉及到了兩個比較重要的概念,那就是Kotlin的函數類型Lambda表達式

其實,Lambda表達式是函數類型的一個實例。

怎麼說呢,Kotlin中把類似的聲明(T) -> R稱作函數類型。具體點的比如(Int) -> String,就是說這裏的參數是一個函數類型,函數的入參爲Int類型,返回值是String類型。

另外,如果函數類型有多個入參,比如(A, B) -> C。

如果沒有參數的函數類型,則表示爲() -> R。

需要注意的是,函數類型中,如果返回值爲Unit的,不能和函數一樣省略掉。

    fun func(a: Int, p: (p: String) -> Unit) {

    }

這樣是OK的,但是如果Unit不寫,編譯器就會提示缺少類型聲明。

另外中函數類型的表示方法爲A.(B) -> C,表示A調用的是一個(B)-> C的返回值,這種情況下,很多時候用於C是一個函數類型。也稱作“帶有接收者的函數字面值”,聽起來很玄乎的,其實和擴展函數類似。

val sum: Int.(Int) -> Int = { other -> plus(other) }

是不是看不太懂,用擴展函數重寫一下

    fun Int.p(p: Int) {
        plus(p)
    }

或者寫成這樣

val sum = fun Int.(other: Int): Int = this + other

再來重新一下第一個

     val sum: Int.(Int) -> Int = {
         other -> this.plus(other)
     }

this就代表的是Int,最終就是A類型調用它的擴展函數。看一個比較高大上的

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

被稱之爲Kotlin語法糖之一的let函數。關於Kotlin語法糖,我們下一篇單獨來說,看完之後,你就會對Kotlin深愛。

另外有一個比較特殊的函數類型,使用suspend來修飾的,稱爲掛起函數。掛起函數用在協程當中,協程是Kotlin異步的最佳實踐。我們後面會抽專門的篇幅來介紹協程。

關於函數類型,還有幾個點:

  1. 如需將函數類型指定爲可空,請使用圓括號:((Int, Int) -> Int)?
  2. 函數類型可以使用圓括號進行接合:(Int) -> ((Int) -> Unit)
  3. 箭頭表示法是右結合的,(Int) -> (Int) -> Unit 與前述示例等價,但不等於 ((Int) -> (Int)) -> Unit
  4. 可以通過使用類型別名給函數類型起一個別稱。
typealias ClickHandler = (Button, ClickEvent) -> Unit

有別稱的函數類型,後續調用可以直接使用別稱。

關於函數類型說完了,總結一下,如果一個函數需要傳入函數類型,有哪些辦法可以得到函數類型的實例傳入呢?

  1. Lambda表達式和匿名函數。
  2. 使用已有聲明的可調用引用。
  • 頂層、局部、成員、擴展函數:::isOdd、 String::toInt
  • 頂層、成員、擴展屬性:List<Int>::size
  • 構造函數:::Regex
  1. 使用實現函數類型接口的自定義類的實例,如
class IntTransformer: (Int) -> Int {
    override operator fun invoke(x: Int): Int = TODO()
}

val intFunction: (Int) -> Int = IntTransformer()

需要注意的是,在獲取函數類型實例時,如果有足夠的信息使得編譯器能夠推斷出類型的話,其類型可以省略。

函數類型的值可以通過其 invoke(……) 操作符調用:f.invoke(x) 或者直接 f(x)

上面已經提及好幾次Lambda表達式了,接下來,就看看Lambda表達式是如何定義的。

先引用官方原話:lambda 表達式與匿名函數是“函數字面值”,即未聲明的函數, 但立即做爲表達式傳遞。

然後具體舉例說明。

說是“函數字面值”,也就是說Lambda表達式就相當於一個變量,用沒有名字的函數表示了,相當於

{a, b -> a + b}

簡單來說,就一段代碼塊。

對於匿名函數來說,就比lambda表達式多了一個fun關鍵字。

            val sum: (Int, Int) -> Int = {a, b ->
                a + b
            }
            val sum2 = fun (a: Int, b: Int): Int = a + b

另外,當lambda表達式的參數唯一時,可以省略圓括弧。

如可以使用的標準庫函數

run {print("Hello")}

另外,lambda表達式參數唯一時,如果編譯器自己可以識別出簽名,也可以不用聲明唯一的參數並忽略 ->。 該參數會隱式聲明爲 it

            val sum: (Int) -> Int = {
                1
            }

如果使用編譯器,則可以看到編譯器對應的提示

Lambda 表達式或者匿名函數(以及局部函數和對象表達式) 可以訪問其閉包 ,即在外部作用域中聲明的變量。

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

是不是覺得Kotlin的魅力越來越大啦

關於函數類型與Lambda表達式,就說到這兒,下一篇,學習Kotlin的語法糖,讓你徹底愛上它。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章