Kotlin lambda表達式
高階函數和 lambda 表達式
高階函數
高階函數是將函數用作參數或返回值的函數。
這種函數的一個很好的例子是 lock()
,它接受一個鎖對象和一個函數,獲取鎖,運行函數並釋放鎖:
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
讓我們來檢查上面的代碼:body
擁有函數類型:() -> T
,
所以它應該是一個不帶參數並且返回 T
類型值的函數。
它在 try{: .keyword }-代碼塊內部調用、被 lock
保護,其結果由lock()
函數返回。
如果我們想調用 lock()
函數,我們可以把另一個函數傳給它作爲參數(參見函數引用):
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
通常會更方便的另一種方式是傳一個 lambda 表達式:
val result = lock(lock, { sharedResource.operation() })
Lambda 表達式在下文會有更詳細的描述,但爲了繼續這一段,下面來看看一個簡短的概述:
- lambda 表達式總是被大括號括着,
- 其參數(如果有的話)在
->
之前聲明(參數類型可以省略), - 函數體(如果存在的話)在
->
後面。
在 Kotlin 中有一個約定,如果函數的最後一個參數是一個函數,並且你傳遞一個 lambda 表達式作爲相應的參數,你可以在圓括號之外指定它:
lock (lock) {
sharedResource.operation()
}
高階函數的另一個例子是 map()
:
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
該函數可以如下調用:
val doubled = ints.map { value -> value * 2 }
請注意,如果 lambda 是該調用的唯一參數,則調用中的圓括號可以完全省略。
it
:單個參數的隱式名稱
另一個有用的約定是,如果函數字面值只有一個參數,
那麼它的聲明可以省略(連同 ->
),其名稱是 it
。
ints.map { it * 2 }
這些約定可以寫LINQ-風格的代碼:
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
下劃線用於未使用的變量(自 1.1 起)
如果 lambda 表達式的參數未使用,那麼可以用下劃線取代其名稱:
map.forEach { _, value -> println("$value!") }
在 lambda 表達式中解構(自 1.1 起)
在 lambda 表達式中解構是作爲解構聲明)的一部分描述的。
內聯函數
使用內聯函數有時能提高高階函數的性能。
Lambda 表達式和匿名函數
一個 lambda 表達式或匿名函數是一個「函數字面值」,即一個未聲明的函數,
但立即做爲表達式傳遞。考慮下面的例子:
max(strings, { a, b -> a.length < b.length })
函數 max
是一個高階函數,換句話說它接受一個函數作爲第二個參數。
其第二個參數是一個表達式,它本身是一個函數,即函數字面值。寫成函數的話,它相當於
fun compare(a: String, b: String): Boolean = a.length < b.length
函數類型
對於接受另一個函數作爲參數的函數,我們必須爲該參數指定函數類型。
例如上述函數 max
定義如下:
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
參數 less
的類型是 (T, T) -> Boolean
,即一個接受兩個類型T
的參數並返回一個布爾值的函數:
如果第一個參數小於第二個那麼該函數返回 true。
在上面第 4 行代碼中,less
作爲一個函數使用:通過傳入兩個 T
類型的參數來調用。
如上所寫的是就函數類型,或者可以有命名參數,如果你想文檔化每個參數的含義的話。
val compare: (x: T, y: T) -> Int = ……
Lambda 表達式語法
Lambda 表達式的完整語法形式,即函數類型的字面值如下:
val sum = { x: Int, y: Int -> x + y }
lambda 表達式總是被大括號括着,
完整語法形式的參數聲明放在括號內,並有可選的類型標註,
函數體跟在一個 ->
符號之後。如果推斷出的該 lambda 的返回類型不是 Unit
,那麼該 lambda 主體中的最後一個(或可能是單個)表達式會視爲返回值。
如果我們把所有可選標註都留下,看起來如下:
val sum: (Int, Int) -> Int = { x, y -> x + y }
一個 lambda 表達式只有一個參數是很常見的。
如果 Kotlin 可以自己計算出簽名,它允許我們不聲明唯一的參數,並且將隱含地
爲我們聲明其名稱爲 it
:
ints.filter { it > 0 } // 這個字面值是「(it: Int) -> Boolean」類型的
我們可以使用限定的返回語法從 lambda 顯式返回一個值。否則,將隱式返回最後一個表達式的值。因此,以下兩個片段是等價的:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
請注意,如果一個函數接受另一個函數作爲最後一個參數,lambda 表達式參數可以在
圓括號參數列表之外傳遞。
參見 callSuffix 的語法。
匿名函數
上面提供的 lambda 表達式語法缺少的一個東西是指定函數的返回類型的
能力。在大多數情況下,這是不必要的。因爲返回類型可以自動推斷出來。然而,如果
確實需要顯式指定,可以使用另一種語法: 匿名函數 。
fun(x: Int, y: Int): Int = x + y
匿名函數看起來非常像一個常規函數聲明,除了其名稱省略了。其函數體
可以是表達式(如上所示)或代碼塊:
fun(x: Int, y: Int): Int {
return x + y
}
參數和返回類型的指定方式與常規函數相同,除了
能夠從上下文推斷出的參數類型可以省略:
ints.filter(fun(item) = item > 0)
匿名函數的返回類型推斷機制與正常函數一樣:對於具有表達式函數體的匿名函數將自動
推斷返回類型,而具有代碼塊函數體的返回類型必須顯式
指定(或者已假定爲 Unit
)。
請注意,匿名函數參數總是在括號內傳遞。 允許將函數
留在圓括號外的簡寫語法僅適用於 lambda 表達式。
Lambda表達式和匿名函數之間的另一個區別是
非局部返回的行爲。一個不帶標籤的 return{: .keyword } 語句
總是在用 fun{: .keyword } 關鍵字聲明的函數中返回。這意味着 lambda 表達式中的 return{: .keyword }
將從包含它的函數返回,而匿名函數中的 return{: .keyword }
將從匿名函數自身返回。
閉包
Lambda 表達式或者匿名函數(以及局部函數和對象表達式)
可以訪問其 閉包 ,即在外部作用域中聲明的變量。 與 Java 不同的是可以修改閉包中捕獲的變量:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
帶接收者的函數字面值
Kotlin 提供了使用指定的 接收者對象 調用函數字面值的功能。
在函數字面值的函數體中,可以調用該接收者對象上的方法而無需任何額外的限定符。
這類似於擴展函數,它允你在函數體內訪問接收者對象的成員。
其用法的最重要的示例之一是類型安全的 Groovy-風格構建器。
這樣的函數字面值的類型是一個帶有接收者的函數類型:
sum : Int.(other: Int) -> Int
該函數字面值可以這樣調用,就像它是接收者對象上的一個方法一樣:
1.sum(2)
匿名函數語法允許你直接指定函數字面值的接收者類型
如果你需要使用帶接收者的函數類型聲明一個變量,並在之後使用它,這將非常有用。
val sum = fun Int.(other: Int): Int = this + other
當接收者類型可以從上下文推斷時,lambda 表達式可以用作帶接收者的函數字面值。
class HTML {
fun body() { …… }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 創建接收者對象
html.init() // 將該接收者對象傳給該 lambda
return html
}
html { // 帶接收者的 lambda 由此開始
body() // 調用該接收者對象的一個方法
}