函數類型與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異步的最佳實踐。我們後面會抽專門的篇幅來介紹協程。
關於函數類型,還有幾個點:
- 如需將函數類型指定爲可空,請使用圓括號:
((Int, Int) -> Int)?
- 函數類型可以使用圓括號進行接合:
(Int) -> ((Int) -> Unit)
- 箭頭表示法是右結合的,
(Int) -> (Int) -> Unit
與前述示例等價,但不等於((Int) -> (Int)) -> Unit
。 - 可以通過使用類型別名給函數類型起一個別稱。
typealias ClickHandler = (Button, ClickEvent) -> Unit
有別稱的函數類型,後續調用可以直接使用別稱。
關於函數類型說完了,總結一下,如果一個函數需要傳入函數類型,有哪些辦法可以得到函數類型的實例傳入呢?
- Lambda表達式和匿名函數。
- 使用已有聲明的可調用引用。
- 頂層、局部、成員、擴展函數:
::isOdd
、String::toInt
, - 頂層、成員、擴展屬性:
List<Int>::size
, - 構造函數:
::Regex
- 使用實現函數類型接口的自定義類的實例,如
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的語法糖,讓你徹底愛上它。