lambda 與成員引用

總述

  1. lambda 編譯后生成的類都繼承 Lambda 類缝裁,并根據(jù)參數(shù)個(gè)數(shù)實(shí)現(xiàn) FunctionN 接口 —— N 表示參數(shù)個(gè)數(shù)嘹黔,所以 lambda 的實(shí)際類型是 FunctionN滚婉。

  2. 屬性成員引用的實(shí)際類型是 PropertyReference1 子類酸舍,而后者實(shí)現(xiàn)了 KProperty1 责嚷,KProperty1 定義如下:

    public interface KProperty1<T, out R> : KProperty<R>, (T) -> R
    

由于 PropertyRefrence1 外界無(wú)法使用莱睁,所以可以認(rèn)為 屬性成員引用的實(shí)質(zhì)類型是 KProperty1痕支。

因此颁虐,屬性成員可直接賦值給初始值為 lambda 表達(dá)式的變量,但反之不行

屬性初始引用與 lambda
  1. 方法成員引用的實(shí)際類型是 KFunctionN卧须,但編譯后生成的類實(shí)現(xiàn)了函數(shù)類型接口另绩,因此方法成員引用可直接賦值給初始值為 lambda 的變量儒陨,但反之不行
方法成員引用與 lambda
  1. lambda 是函數(shù)類型的實(shí)例,成員引用是函數(shù)類型的子類的實(shí)例笋籽。因此成員引用可直接賦值給 lambda蹦漠,但反之不行

  2. 函數(shù)類型變量的運(yùn)行其實(shí)質(zhì)就是運(yùn)行其內(nèi)部的 invoke 方法 —— 參考 invoke 約定车海。


lambda

lambda 表達(dá)式的本質(zhì)是 可以傳遞給其他函數(shù)的小段代碼

lambda 是函數(shù)類型的實(shí)例笛园,因此可以直接賦值給函數(shù)類型的變量。

每一個(gè) lambda 都會(huì)被編譯成一個(gè)匿名內(nèi)部類侍芝,傳入 lambda 的地方就是傳入一個(gè)該類的實(shí)例研铆。

fun test(id:String){
    Thread{ // 使用 lambda
        println("runnable")
    }.start()
}
// 創(chuàng)建一個(gè)內(nèi)部類
class Test$1 : Runnable{
    override fun run() {
        println("runnable")
    }
}

fun test(id:String){
    Thread(Test$1()).start()// 實(shí)際上是傳入了一個(gè)內(nèi)部類的實(shí)例
}

語(yǔ)法

  1. 始終用花括號(hào)包圍。參數(shù)列表不需要用括號(hào)括起來(lái)竭贩。使用箭頭把參數(shù)列表與函數(shù)體分開(kāi)蚜印。

如圖:

語(yǔ)法
  1. lambda 表達(dá)式中可以含有多個(gè)表達(dá)式,只有最后表達(dá)式的結(jié)果是 lambda 的返回值留量。

使用

  1. 可以直接賦值給一個(gè)變量窄赋,然后將這個(gè)變量當(dāng)作普通函數(shù);或者直接運(yùn)行表達(dá)式

    fun main(args: Array<String>) {
        val l = {x:Int,y:Int -> x+y}
        // 此處要加分號(hào)楼熄。不然會(huì)認(rèn)為下個(gè)表達(dá)式是 println() 的一個(gè)參數(shù)
        println(l(2,3));
        { println("xx")}()
    }
    
  2. 如果表達(dá)式是最后一個(gè)參數(shù)忆绰,可以將表達(dá)式放到括號(hào)外面;如果只有表達(dá)式一個(gè)參數(shù)可岂,可以省略小括號(hào)错敢。如果要傳遞兩個(gè)或更多的表達(dá)式,不能將表達(dá)式定義在小括號(hào)外面缕粹。

    fun main(args: Array<String>) {
        val list = listOf(Person(1), Person(3), Person(2))
        // 正常傳遞表達(dá)式
        println(list.maxBy({ p: Person -> p.age }))
        // 將表達(dá)式提取到括號(hào)外面
        println(list.maxBy() {p: Person -> p.age  })
        // 只有一個(gè)參數(shù)稚茅,省略小括號(hào)
        println(list.maxBy { p: Person -> p.age })
    }
    
  3. 如果能推導(dǎo)出參數(shù)類型,則可以省略表達(dá)式中的參數(shù)類型平斩;如果只有一個(gè)參數(shù)亚享,且類型可以推導(dǎo)出類型,可以使用 it 指代參數(shù)绘面,連參數(shù)名都不用寫欺税,直接使用 it。接上例揭璃,可以再簡(jiǎn)化:

        // 能推導(dǎo)出類型晚凿,則省略參數(shù)類型
        println(list.maxBy { person -> person.age })
        // 能推導(dǎo)出類型,且只有一個(gè)參數(shù)瘦馍,則可以省略參數(shù)名使用 it 指代
        println(list.maxBy { it.age })
    
  4. 如果不需要參數(shù)歼秽,可以省略 -> ,直接寫表達(dá)式的執(zhí)行語(yǔ)句:

    fun main(args: Array<String>) {
        var a = 10
        val f = { println("$a") }
    }
    

訪問(wèn)變量

與對(duì)象表達(dá)式一樣情组,表達(dá)式可以訪問(wèn)這個(gè)函數(shù)的參數(shù)哲银,以及定義在表達(dá)式之前的局部變量扛吞。

kt 中允許表達(dá)式訪問(wèn)非 final 變量(通過(guò) var 聲明的變量),并修改這些變量荆责。

fun test(name: String){
    var age = 10
    // 可以訪問(wèn)參數(shù) name,也可以訪問(wèn)定義在表達(dá)式前的 age 亚脆,但不能訪問(wèn) sex
    { println("${name}  ${age}")}()

    var sex = "f"
}

實(shí)現(xiàn)原理

  1. 當(dāng)表達(dá)式捕獲 final 變量時(shí)做院,變量的值會(huì)被復(fù)制下來(lái),和使用這個(gè)值的表達(dá)式一起存儲(chǔ)濒持。java 中也是這個(gè)原理键耕。

  2. 對(duì)于非 final 變量,它的值會(huì)被封裝在一個(gè)特殊的包裝器中柑营,將包裝器的引用和表達(dá)式一起存儲(chǔ)屈雄。在整個(gè)過(guò)程中,引用不會(huì)發(fā)生變化官套,但包裝器中的屬性可以改變酒奶。

    // 包裝器
    class Ref<T>(var value:T)
    
    fun test(name: String){
        val age = 10
        val ref = Ref(age)
        val lambda = {ref.value++}
    }
    

    與表達(dá)式一起存儲(chǔ)的是 ref,ref 在整個(gè)過(guò)程中都不會(huì)發(fā)生變化奶赔,但其屬性 value 卻可以改變惋嚎。


成員引用

它提供了一個(gè)簡(jiǎn)明語(yǔ)法,來(lái)創(chuàng)建一個(gè)調(diào)用單個(gè)方法或者訪問(wèn)單個(gè)屬性的函數(shù)站刑。

成員引用在編譯后會(huì)生成一個(gè)內(nèi)部類的實(shí)例(該內(nèi)部類有 invoke 方法)另伍,執(zhí)行成員引用就相當(dāng)于執(zhí)行 invoke 方法,而 invoke 方法內(nèi)部會(huì)調(diào)用引用的函數(shù)(函數(shù)成員引用時(shí))或者訪問(wèn)指定的屬性(屬性成員引用時(shí))绞旅。

相較于普通的直接調(diào)用方法和引用屬性摆尝,成員引用是一個(gè)函數(shù)類型的實(shí)例,因此可以將成員引用作為實(shí)參傳遞給函數(shù)類型的形參:

fun main(args: Array<String>) {
    val t = Obj::a
    println(t is (Obj) -> Int) // true

    test(t) // 11
}

fun test(a: (Obj) -> Int) {
    val obj = Obj(11)
    println(a(obj)// a() 相當(dāng)于執(zhí)行成員引用因悲,所以會(huì)獲取到對(duì)應(yīng)的屬性值
}

class Obj(val a: Int)

其格式如下:

成員引用
  1. 無(wú)論 member 是函數(shù)還是屬性堕汞,其后都不加 ()

  2. 如果是頂層函數(shù)、屬性囤捻,則省略 Class 臼朗,直接以 :: 開(kāi)頭。

  3. 如果引用的是構(gòu)造函數(shù)蝎土,則寫成 ::類名视哑。

  4. 成員引用一樣適用于擴(kuò)展函數(shù)、屬性誊涯。

使用

成員引用會(huì)創(chuàng)建一個(gè)函數(shù)挡毅,執(zhí)行該函數(shù)時(shí),會(huì)執(zhí)行指定的方法或訪問(wèn)指定的屬性暴构。如下例跪呈,main 函數(shù)中的 get 與 age 都是函數(shù)段磨,執(zhí)行 get 時(shí)會(huì)執(zhí)行 person.get() ,而執(zhí)行 age 時(shí)會(huì)獲取 person 中的 age 屬性的值耗绿。

  1. kt 1.0 中苹支,執(zhí)行成員引用時(shí),始終需要提供一個(gè)該類的實(shí)例误阻。

  2. kt 1.1 中债蜜,可以直接使用使用對(duì)象進(jìn)行定義,如下例中的 age1 與 get1 的定義方式究反。在執(zhí)行時(shí)寻定,不需要再傳入實(shí)例對(duì)象。

fun main(args: Array<String>) {
    val person = Person(1111)

    val age = Person::age
    println(age(person))
    val get = Person::get
    println(get(person))

    val age1 = person::age
    println(age1())
    val get1 = person::get
    println(get1())
}

data class Person(val age: Int) {
    fun get() = age * 10
}

與 lambda 比較

成員引用與 lambda 是互通的精耐,可以互相使用

無(wú)論是成員引用還是 lambda 狼速,其實(shí)現(xiàn)時(shí)都會(huì)轉(zhuǎn)成 Function 的實(shí)例。因此卦停,它們是同一類型向胡,可以相互賦值 —— 只要泛型一致即可。如下沫浆,所有的輸出都是 true:

fun main(args: Array<String>) {
    val t = Obj::a
    val l = { a: Int, b: Int -> a + b }
    println(t is Function<*>)
    println(l is Function2<*, *, *>)
    val f = Obj::test
    println(f is Function<*>)
    println(f is Function1<*, *>)
}

class Obj(val a: Int) {
    fun test() {}
}

如定義一個(gè) test() 函數(shù)接收一個(gè)表達(dá)式捷枯。可以直接傳入一個(gè)表達(dá)式专执,也可以傳入一個(gè)成員引用淮捆。

fun main(args: Array<String>) {
    val person = Person(1111)

    test(person) { it.get() }
    test(person,Person::get)
}

data class Person(val age: Int) {
    fun get() = age * 10
}

// 該方法接收一個(gè) lambda 表達(dá)式
fun test(p: Person, a: (Person) -> Int) = println(a(p))

可以發(fā)現(xiàn)上述的表達(dá)式很簡(jiǎn)單,直接調(diào)用了 Person 的 get 方法本股。這種情況下攀痊,使用成員引用更方便。

當(dāng)需要定義的表達(dá)式的功能已經(jīng)被別的方法實(shí)現(xiàn)過(guò)拄显,但使用表達(dá)式的函數(shù)只接收表達(dá)式時(shí)苟径,可以使用成員引用。如上例中的 test 方法躬审,它接收表達(dá)式棘街,而表達(dá)式要實(shí)現(xiàn)的功能是 Person#get() 方法已經(jīng)實(shí)現(xiàn)過(guò)的,所以直接使用成員引用即可承边。


lambda 管理資源

常見(jiàn)模式是:先獲取一個(gè)資源遭殉,完成一個(gè)操作后,關(guān)閉該資源博助。一般在 try 中獲取資源险污,將操作封裝成 lambda 表達(dá)式,然后在 finally 中關(guān)閉資源。

如下:首先自動(dòng)加鎖蛔糯,然后執(zhí)行操作拯腮,操作執(zhí)行完成后釋放鎖蚁飒。這樣封裝了加鎖狼电、釋放鎖的邏輯凸椿,調(diào)用者只關(guān)注自己要完成的操作咙崎。

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

帶接收者的 lambda

lambda 表達(dá)式執(zhí)行時(shí)网杆,this 指向接收者對(duì)象。因此,凡是調(diào)用接收者中的方法,都可以直接調(diào)用

首先看一般情況下的 lambda 的定義:

```kotlin
fun main(args: Array<String>) {
    test{
        it.append("xxxx")
    }
}

fun test(b: (StringBuilder) -> Unit) {
    val sb = StringBuilder()
    b(sb)
    println(sb.toString())
}
```

調(diào)用 test 傳入的表達(dá)式中冠胯,想要調(diào)用 sb 中的 append 方法奈搜,必須使用 it.append秋秤。使用帶接收者的 lambda 后绍哎,如下:

fun main(args: Array<String>) {
    test{
        append("xxxx")
    }
}

fun test(b: StringBuilder.() -> Unit) {
    val sb = StringBuilder()
    b(sb)
    println(sb.toString())
 }

可以直接調(diào)用 append() 海诲,而不需要使用 it.append()。

上例中蚯斯,StringBuilder 是接收者類型,test() 中定義的 sb 就是接收者對(duì)象零院。

  1. 接收者可以當(dāng)作參數(shù)傳遞到表達(dá)式中溉跃,也可以直接使用接收者點(diǎn)的表達(dá)式名的形式進(jìn)行調(diào)用:

    fun buildStr(action: StringBuilder.() -> Unit) {
        val sb = StringBuilder()
        action(sb)
        sb.action()
        println(sb.toString())
    }
    
  2. 將函數(shù)參數(shù)中的一個(gè)參數(shù)類型移到括號(hào)外面,并使用 . 將它與其余參數(shù)區(qū)分開(kāi)告抄。

定義方式
  1. 由擴(kuò)展函數(shù)類似撰茎,當(dāng)函數(shù)或 lambda 被調(diào)用時(shí)需要提供這個(gè)對(duì)象,它在函數(shù)體內(nèi)是可用的打洼。

lambda 轉(zhuǎn)成類

如果一個(gè) lambda 太復(fù)雜龄糊,可以將 lambda 轉(zhuǎn)成實(shí)現(xiàn)了函數(shù)類型接口的的類,并重寫其 invoke 方法

這種方法的優(yōu)點(diǎn)時(shí):從 lambda 體中抽取的函數(shù)的作用域盡可能的小募疮,它僅在判斷式內(nèi)部可見(jiàn):

fun main(args: Array<String>) {
    val test = arrayOf("a", "bb", "CC", "drewAA")
    for (a in test.filter(Test())) {
        println(a) // a CC
    }
}

class Test : (String) -> Boolean {
    override fun invoke(p1: String): Boolean = p1.startsWith("a") || isImportant(p1)

    private fun isImportant(s: String): Boolean = s.toUpperCase() === s
}

filter 要的是一個(gè)函數(shù)類型的實(shí)例炫惩,所以此處可以傳入 lambda 表達(dá)式或者一個(gè)函數(shù)類型的子類 —— 本例中使用的是后者。

在 Test 的類中阿浓,invoke 會(huì)被 filter 調(diào)用他嚷,而 invoke 中又調(diào)用了 isImportant 方法 —— 這就是比 lambda 要方便的地方,可以在類中定義方法。


lambda 中的 return

只有調(diào)用 lambda 的函數(shù)是 inline 函數(shù)筋蓖,才能在 lambda 中使用 return

lambda 中使用 return 時(shí)卸耘,會(huì)直接結(jié)束使用 lambda 的函數(shù),而不是只結(jié)束 lambda 表達(dá)式粘咖。可以在表達(dá)式外層套一個(gè) run 函數(shù)蚣抗,return 時(shí)只返回 run 標(biāo)簽。如下述代碼只會(huì)輸出 run 瓮下,不會(huì)輸出 test翰铡。因?yàn)榈诙螆?zhí)行 test 時(shí),調(diào)用了 return讽坏,會(huì)直接結(jié)束掉 main 方法锭魔。第一次只是結(jié)束掉 run 方法,所以還會(huì)執(zhí)行下面的語(yǔ)句:

fun main(args: Array<String>) {

    run {
        test {
            return@run
        }
    }
    println("run")

    test {
        return
    }
    println("test")
}
inline fun test(t: (Int) -> Unit) {
    t(22)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末路呜,一起剝皮案震驚了整個(gè)濱河市赂毯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拣宰,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烦感,死亡現(xiàn)場(chǎng)離奇詭異巡社,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)手趣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門晌该,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绿渣,你說(shuō)我怎么就攤上這事朝群。” “怎么了中符?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵姜胖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我淀散,道長(zhǎng)右莱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任档插,我火速辦了婚禮慢蜓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘郭膛。我一直安慰自己晨抡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著耘柱,像睡著了一般如捅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帆谍,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天伪朽,我揣著相機(jī)與錄音,去河邊找鬼汛蝙。 笑死烈涮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窖剑。 我是一名探鬼主播坚洽,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼西土!你這毒婦竟也來(lái)了讶舰?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤需了,失蹤者是張志新(化名)和其女友劉穎跳昼,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體肋乍,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鹅颊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了墓造。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堪伍。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖觅闽,靈堂內(nèi)的尸體忽然破棺而出帝雇,到底是詐尸還是另有隱情,我是刑警寧澤蛉拙,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布尸闸,位于F島的核電站,受9級(jí)特大地震影響刘离,放射性物質(zhì)發(fā)生泄漏室叉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一硫惕、第九天 我趴在偏房一處隱蔽的房頂上張望茧痕。 院中可真熱鬧,春花似錦恼除、人聲如沸踪旷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)令野。三九已至舀患,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間气破,已是汗流浹背聊浅。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留现使,地道東北人低匙。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像碳锈,于是被迫代替她去往敵國(guó)和親顽冶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351