高階函數(shù)和 lambda 表達式

高階函數(shù)和 lambda 表達式

高階函數(shù)

高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù)废菱。
這種函數(shù)的一個很好的例子是 lock()咖刃,它接受一個鎖對象和一個函數(shù)差油,獲取鎖拗军,運行函數(shù)并釋放鎖:

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

讓我們來檢查上面的代碼:body 擁有函數(shù)類型() -> T
所以它應(yīng)該是一個不帶參數(shù)并且返回 T 類型值的函數(shù)。
它在 try{: .keyword }-代碼塊內(nèi)部調(diào)用发侵、被 lock 保護侈咕,其結(jié)果由lock()函數(shù)返回。

如果我們想調(diào)用 lock() 函數(shù)器紧,我們可以把另一個函數(shù)傳給它作為參數(shù)(參見函數(shù)引用):

fun toBeSynchronized() = sharedResource.operation()

val result = lock(lock, ::toBeSynchronized)

通常會更方便的另一種方式是傳一個 lambda 表達式

val result = lock(lock, { sharedResource.operation() })

Lambda 表達式在下文會有更詳細(xì)的描述,但為了繼續(xù)這一段楼眷,讓我們看一個簡短的概述:

  • lambda 表達式總是被大括號括著铲汪,
  • 其參數(shù)(如果有的話)在 -> 之前聲明(參數(shù)類型可以省略),
  • 函數(shù)體(如果存在的話)在 -> 后面罐柳。

在 Kotlin 中有一個約定掌腰,如果函數(shù)的最后一個參數(shù)是一個函數(shù),并且你傳遞一個 lambda 表達式作為相應(yīng)的參數(shù)张吉,你可以在圓括號之外指定它

lock (lock) {
    sharedResource.operation()
}

//原始
lock(lock, {sharedResource.operation()})

高階函數(shù)的另一個例子是 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
}

該函數(shù)可以如下調(diào)用:

val doubled = ints.map { value -> value * 2 }

//原始
val doubled = ints.map({ value -> value * 2 })
//最后一個參數(shù)是函數(shù)齿梁,并且傳遞lambda作為參數(shù),可以在圓括號之外指定
val doubled = ints.map(){ value -> value * 2}
//lambda是唯一參數(shù)肮蛹,圓括號可以省略
val doubled = ints.map{ value -> value * 2}

請注意勺择,如果 lambda 是該調(diào)用的唯一參數(shù),則調(diào)用中的圓括號可以完全省略伦忠。

it:單個參數(shù)的隱式名稱

另一個有用的約定是省核,如果函數(shù)字面值只有一個參數(shù),
那么它的聲明可以省略(連同 ->)昆码,其名稱是 it气忠。

ints.map { it * 2 }

這些約定可以寫LINQ-風(fēng)格的代碼:

strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }

下劃線用于未使用的變量(自 1.1 起)

如果 lambda 表達式的參數(shù)未使用,那么可以用下劃線取代其名稱:

map.forEach { _, value -> println("$value!") }

在 lambda 表達式中解構(gòu)(自 1.1 起)

在 lambda 表達式中解構(gòu)是作為解構(gòu)聲明的一部分描述的赋咽。

內(nèi)聯(lián)函數(shù)

使用內(nèi)聯(lián)函數(shù)有時能提高高階函數(shù)的性能旧噪。

Lambda 表達式和匿名函數(shù)

一個 lambda 表達式或匿名函數(shù)是一個“函數(shù)字面值”,即一個未聲明的函數(shù)脓匿,
但立即做為表達式傳遞淘钟。考慮下面的例子:

max(strings, { a, b -> a.length < b.length })

函數(shù) max 是一個高階函數(shù)亦镶,換句話說它接受一個函數(shù)作為第二個參數(shù)日月。
其第二個參數(shù)是一個表達式,它本身是一個函數(shù)缤骨,即函數(shù)字面值爱咬。寫成函數(shù)的話,它相當(dāng)于

fun compare(a: String, b: String): Boolean = a.length < b.length

函數(shù)類型

對于接受另一個函數(shù)作為參數(shù)的函數(shù)绊起,我們必須為該參數(shù)指定函數(shù)類型精拟。
例如上述函數(shù) 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
}

參數(shù) less 的類型是 (T, T) -> Boolean,即一個接受兩個類型T的參數(shù)并返回一個布爾值的函數(shù):
如果第一個參數(shù)小于第二個那么該函數(shù)返回 true。

在上面第 4 行代碼中蜂绎,less 作為一個函數(shù)使用:通過傳入兩個 T 類型的參數(shù)來調(diào)用栅表。

如上所寫的是就函數(shù)類型,或者可以有命名參數(shù)师枣,如果你想文檔化每個參數(shù)的含義的話怪瓶。

val compare: (x: T, y: T) -> Int = ……
var sum: ((Int, Int) -> Int)? = null

Lambda 表達式語法

Lambda 表達式的完整語法形式,即函數(shù)類型的字面值如下:

val sum = { x: Int, y: Int -> x + y }

lambda 表達式總是被大括號括著践美,
完整語法形式的參數(shù)聲明放在括號內(nèi)洗贰,并有可選的類型標(biāo)注,
函數(shù)體跟在一個 -> 符號之后陨倡。如果推斷出的該 lambda 的返回類型不是 Unit敛滋,那么該 lambda 主體中的最后一個(或可能是單個)表達式會視為返回值。

如果我們把所有可選標(biāo)注都留下兴革,看起來如下:

val sum: (Int, Int) -> Int = { x, y -> x + y }

一個 lambda 表達式只有一個參數(shù)是很常見的绎晃。

ints.filter { it > 0 } // 這個字面值是“(it: Int) -> Boolean”類型的

我們可以使用限定的返回語法從 lambda 顯式返回一個值。否則杂曲,將隱式返回最后一個表達式的值庶艾。因此,以下兩個片段是等價的:

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

參見 callSuffix 的語法擎勘。

匿名函數(shù)

-->確實需要顯式指定落竹,可以使用另一種語法: 匿名函數(shù)

fun(x: Int, y: Int): Int = x + y
fun(x: Int, y: Int): Int {
    return x + y
}
ints.filter(fun(item) = item > 0)

-->指定(或者已假定為 Unit)货抄。

-->總是在用 fun{: .keyword } 關(guān)鍵字聲明的函數(shù)中返回述召。這意味著 lambda 表達式中的 return{: .keyword }
將從包含它的函數(shù)返回,而匿名函數(shù)中的 return{: .keyword }
將從匿名函數(shù)自身返回蟹地。

閉包

Lambda 表達式或者匿名函數(shù)(以及局部函數(shù)對象表達式
可以訪問其 閉包 积暖,即在外部作用域中聲明的變量。 與 Java 不同的是可以修改閉包中捕獲的變量:

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

帶接收者的函數(shù)字面值

Kotlin 提供了使用指定的 接收者對象 調(diào)用函數(shù)字面值的功能怪与。
在函數(shù)字面值的函數(shù)體中夺刑,可以調(diào)用該接收者對象上的方法而無需任何額外的限定符。
這類似于擴展函數(shù)分别,它允許你在函數(shù)體內(nèi)訪問接收者對象的成員遍愿。
其用法的最重要的示例之一是類型安全的 Groovy-風(fēng)格構(gòu)建器

這樣的函數(shù)字面值的類型是一個帶有接收者的函數(shù)類型:

sum : Int.(other: Int) -> Int

該函數(shù)字面值可以這樣調(diào)用耘斩,就像它是接收者對象上的一個方法一樣:

1.sum(2)

匿名函數(shù)語法允許你直接指定函數(shù)字面值的接收者類型沼填。
如果你需要使用帶接收者的函數(shù)類型聲明一個變量,并在之后使用它括授,這將非常有用坞笙。

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

當(dāng)接收者類型可以從上下文推斷時岩饼,lambda 表達式可以用作帶接收者的函數(shù)字面值。

class HTML {
    fun body() { …… }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // 創(chuàng)建接收者對象
    html.init()        // 將該接收者對象傳給該 lambda
    return html
}


html {       // 帶接收者的 lambda 由此開始
    body()   // 調(diào)用該接收者對象的一個方法
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薛夜,一起剝皮案震驚了整個濱河市籍茧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梯澜,老刑警劉巖寞冯,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異晚伙,居然都是意外死亡简十,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門撬腾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恢恼,你說我怎么就攤上這事民傻。” “怎么了场斑?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵漓踢,是天一觀的道長。 經(jīng)常有香客問我漏隐,道長喧半,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任青责,我火速辦了婚禮挺据,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脖隶。我一直安慰自己扁耐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布产阱。 她就那樣靜靜地躺著婉称,像睡著了一般。 火紅的嫁衣襯著肌膚如雪构蹬。 梳的紋絲不亂的頭發(fā)上王暗,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天,我揣著相機與錄音庄敛,去河邊找鬼俗壹。 笑死,一個胖子當(dāng)著我的面吹牛藻烤,可吹牛的內(nèi)容都是我干的策肝。 我是一名探鬼主播肛捍,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼之众!你這毒婦竟也來了拙毫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤棺禾,失蹤者是張志新(化名)和其女友劉穎缀蹄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體膘婶,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡缺前,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悬襟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衅码。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脊岳,靈堂內(nèi)的尸體忽然破棺而出逝段,到底是詐尸還是另有隱情,我是刑警寧澤割捅,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布奶躯,位于F島的核電站,受9級特大地震影響亿驾,放射性物質(zhì)發(fā)生泄漏嘹黔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一莫瞬、第九天 我趴在偏房一處隱蔽的房頂上張望儡蔓。 院中可真熱鬧,春花似錦疼邀、人聲如沸浙值。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽开呐。三九已至,卻和暖如春规求,著一層夾襖步出監(jiān)牢的瞬間筐付,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工阻肿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瓦戚,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓丛塌,卻偏偏與公主長得像较解,于是被迫代替她去往敵國和親畜疾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容