《Kotlin 實(shí)戰(zhàn)》- 5 Lambda 編程

  • lambda 本質(zhì)是可以傳遞給其他函數(shù)的一小段代碼挪哄。

5.1 Lambda 表達(dá)式和成員引用

  • 函數(shù)式編程與 lambda 表達(dá)式:函數(shù)式編程是把函數(shù)當(dāng)做值來對(duì)待可以直接傳遞函數(shù)乏苦;lambda 表達(dá)式使得代碼更簡(jiǎn)潔霎冯,不需要聲明函數(shù),可以高效地直接傳遞代碼塊作為函數(shù)參數(shù)吁津。
button.setOnClickListener {/* 點(diǎn)擊后的執(zhí)行動(dòng)作 */} // 注意是大括號(hào)
  • Lambda 語法:

    { x: Int, y: Int -> x + y}
    
    • 其中 -> 前的部分為參數(shù),后面部分為函數(shù)體。注意到實(shí)參并沒有用括號(hào)括起來距误,實(shí)參和函數(shù)體使用了 -> 符號(hào)隔開簸搞。
    • 可以把 lambda 表達(dá)式存儲(chǔ)在一個(gè)變量中,這個(gè)變量當(dāng)做普通函數(shù)對(duì)待
  • 看一下 lambda 表達(dá)式簡(jiǎn)寫的演變過程:

// 前期準(zhǔn)備
 val people = listOf(Person("Alice", 29), Person("Bob", 31))
 // maxBy 方法的系統(tǒng)聲明
 fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T? 
// 原始方式
people.maxBy({ p: Person -> p.age })
// lambda 表達(dá)式是函數(shù)調(diào)用的最后一個(gè)實(shí)參准潭,可放到括號(hào)外面
people.maxBy(){ p: Person -> p.age }
// lambda 是函數(shù)唯一實(shí)參趁俊,可以去掉空括號(hào)對(duì)
people.maxBy { p: Person -> p.age }
// 與局部變量一樣,如果 lambda 參數(shù)類型可以被推到刑然,就可以省略類型
// 也存在不能被推到的情況寺擂,可以遵循這樣的規(guī)則:先不聲明類型,編譯器報(bào)錯(cuò)后再聲明
people.maxBy {p -> p.age}
// 當(dāng)只有一個(gè)參數(shù)并且參數(shù)類型可以推導(dǎo)泼掠,就可以使用默認(rèn)參數(shù)名稱 it 代替命名參數(shù)
people.maxBy { it.age }
  • 當(dāng)實(shí)參有多個(gè) lambda 時(shí)怔软,不能把超過一個(gè) lambda 放到外面,所以都放在 () 倒是更好的選擇

  • 當(dāng)在函數(shù)內(nèi)聲明一個(gè)匿名 內(nèi)部類的時(shí)候择镇,能夠在這個(gè)匿名類內(nèi)部引這個(gè)函數(shù)的參數(shù)和局部變量挡逼。 也可以用 lambda 做同樣的事情 。 如果在函數(shù)內(nèi)部使用 lambda,也可以訪問這個(gè)外部函數(shù)的參數(shù)以及在 lambda之前定義的局部變量沐鼠。

    • 與 Java 不同的是挚瘟,Kotlin 中不會(huì)僅限于訪問 final 變量,在 lambda 內(nèi)部也可以修改這些變量饲梭。
    • 從 lambda 內(nèi)訪問外部變量乘盖,稱這些變量被 lambda 捕捉
  • 成員引用:Person:: age,類后(比如此處的 age)可以是函數(shù)也可以是屬性憔涉,并且都無需加 ()订框。這樣對(duì)于已經(jīng)定義好的函數(shù),也可以方便的作為參數(shù)傳遞兜叨。

5.2 集合的函數(shù)式API

  • 函數(shù)式編程風(fēng)格在操作集合時(shí)提供了很多優(yōu)勢(shì)穿扳,kotlin 也加入了不少庫函數(shù)來幫助解決集合問題。
  • filter :遍歷集合并選出應(yīng)用給定 lambda 后返回 true 的那些元素(即選出符合條件(lambda 函數(shù))的集合元素国旷,組成新集合
  • map:對(duì)集合中每個(gè)元素應(yīng)用給定的 lambda 并把結(jié)果收集到一個(gè)新集合矛物;
  • all:集合中是否所有元素都符合某條件(lambda),返回值是 boolean 類型跪但;
  • any:集合中是否存在元素符合某條件履羞,同樣返回 Boolean 類型;
  • find:返回第一個(gè)符合條件的元素屡久;
  • count:返回符合條件的元素總個(gè)數(shù)忆首;
  • groupBy:把集合元素按照某特征(lambda)劃分成不同的分組,返回是一個(gè) map被环,key 為 lambda 中的條件糙及,value 是列表集合中的元素;
  • flatMap:把結(jié)合中所有元素按照 lambda 做變化筛欢,然后想得到的結(jié)果“平鋪”浸锨,返回平鋪后的集合唇聘;
  • 注意:使用 lambda 表達(dá)式的代碼看起來簡(jiǎn)單,有時(shí)候卻掩蓋了底層操作的復(fù)雜性揣钦,很容易寫出不必要的重復(fù)計(jì)算的邏輯雳灾,尤其是對(duì)于集合的操作,產(chǎn)生不必要的循環(huán)或重復(fù)冯凹。所以始終要牢記你寫的代碼在干什么谎亩!

5.3 惰性集合操作:序列

  • 上面那些處理鏈表的函數(shù),在鏈?zhǔn)秸{(diào)用時(shí)往往每一步都會(huì)創(chuàng)建新的鏈表宇姚,當(dāng)處理大數(shù)據(jù)量時(shí)匈庭,效率較低。使用序列可以避免創(chuàng)建這些臨時(shí)中間對(duì)象浑劳。

  • Kotlin 惰性集合操作的入口就是 Sequence 接口 阱持。這個(gè)接口表示的就是一個(gè)可以逐個(gè)列舉元素的元素序列。 Sequence 只提供了一 個(gè)方法 iterator魔熏,用來從 序列中獲取值衷咽。Sequence 接口的強(qiáng)大之處在于其操作的實(shí)現(xiàn)方式 。序列中的元素求值是惰性的蒜绽。因此镶骗,可以使用序列更高效地對(duì)集合元素執(zhí)行鏈?zhǔn)讲僮鳎恍枰獎(jiǎng)?chuàng)建額外的集合來保存過程中產(chǎn)生的中間結(jié)果躲雅《︽ⅲ可以調(diào)用擴(kuò)展函數(shù) asSequence 把任意集合轉(zhuǎn)換成序列,調(diào)用 toList 來做反向轉(zhuǎn)換相赁。

  • listOf(1,2,3,4).asSequence().map{ it*it }.filter{ it>3 }.toList()
    
  • 這種操作其實(shí)主要分兩步:中間操作和末端操作相寇。中間操作始終都是惰性的,末端操作(toList() )觸發(fā)執(zhí)行了所有的延期計(jì)算钮科。

  • 原理是“及早求值”唤衫,也就是會(huì)把序列中的元素,依次處理所有過程绵脯,這樣有可能省去部分處理過程战授。比如我們把上例最后的 filter 變?yōu)?find,那么如果是對(duì)集合處理桨嫁,會(huì)先所有元素求平方,再找第一個(gè)大于 3 的元素份帐,而對(duì)于序列處理的話璃吧,會(huì)從第一個(gè)元素開始,先求平方废境,再看結(jié)果是否大于 3畜挨,如此找到第一個(gè)大于 3 的值就宣告結(jié)束了筒繁。

5.4 使用 Java 函數(shù)式接口

  • Kotlin 的 lambda 可以無縫地和 Java Api 互操作。

  • 把只含有一個(gè)方法的接口成為函數(shù)式接口巴元,Android 中 OnClickListener毡咏,java 中比如 Runnable Callable 等都是函數(shù)式接口,Kotlin 允許你在調(diào)用接收函數(shù)式接口作為參數(shù)的方法時(shí)使用 lambda逮刨。

  • // java 中的聲明
    public class View{
      public void setOnClickListener(OnClickListener l){...}
    }
    // Kotlin 調(diào)用
    button.setOnClickListener{...}
    
  • 參數(shù)可以有多個(gè)呕缭,只要含有函數(shù)式接口類型的參數(shù),就可以使用 lambda

  • 實(shí)現(xiàn)原理:在 kotlin 中修己,每個(gè) lambda 表達(dá)式都會(huì)被編譯成一個(gè)匿名類恢总,如果 lambda 捕捉了變量,每個(gè)被捕捉的變量會(huì)在匿名類中有對(duì)應(yīng)的字段睬愤。

5.5 帶接收者的 lambda:with 與 apply

  • With:可以用它對(duì)同一個(gè)對(duì)象執(zhí)行多次操作片仿,而無需反復(fù)把對(duì)象的名稱寫出來。

  • fun alphabet()= with(StringBuilder()){
      for (letter in 'A'..'Z'){
          append(letter)
      }
      toString()
    }
    
    • with 實(shí)際上是一個(gè)接收兩個(gè)參數(shù)的函數(shù)尤辱,這個(gè)例子中兩個(gè)參數(shù)是 StringBuilder 對(duì)象和一個(gè) lambda砂豌,這里利用了把最后的 lambda 放到括號(hào)外的約定,這樣看起來更像是內(nèi)建的語言功能光督。并且示例代碼中 lambda 內(nèi)部省略了 this 引用阳距。
  • apply:和 with 很像,但 apply 始終返回作為實(shí)參傳遞給它的對(duì)象可帽。

  • fun alphabet()= StringBuilder().apply {
      for (letter in 'A'..'Z'){
          append(letter)
      }
    }.toString()
    
  • 二者區(qū)別:

    • 顯然娄涩,with 是庫函數(shù),apply 是擴(kuò)展函數(shù)
    • with 返回的是最后一行表達(dá)式(的值)映跟,apply 返回的是apply 其實(shí)是實(shí)參傳進(jìn)來的對(duì)象(接收者對(duì)象)蓄拣。

5.6 小結(jié)

  • Lambda 允許你把代碼塊當(dāng)作參數(shù)傳遞給函數(shù)。
  • Kotlin 可以把 lambda 放在括號(hào)外傳遞給函數(shù)努隙,而且可以用 it 引用單個(gè)的 lambda 參數(shù)球恤。
  • lambda 中的代碼可以訪問和修改包含這個(gè) lambda 調(diào)用的函數(shù)中的變量。
  • 通過在函數(shù)名稱前加上前綴 ::荸镊,可以創(chuàng)建方法咽斧、構(gòu)造方法及屬性的引用,并用這些引用代替 lambda傳遞給函數(shù)躬存。
  • 使用像 filter张惹、map、all岭洲、any 等函數(shù)宛逗,大多數(shù)公共的集合操作不需要手動(dòng)迭代元素就可以完成。
  • 序列允許你合并一個(gè)集合上的多次操作 盾剩,而不需要?jiǎng)?chuàng)建新的集合來保存中間結(jié)果雷激。
  • 可以把 lambda 作為實(shí)參傳給接收 Java 函數(shù)式接口(帶單抽象方法的接口替蔬,也叫作 SAM 接口)作為形參的方法。
  • 帶接收者的 lambda 是一種特殊的 lambda屎暇,可以在這種 lambda 中直接訪問一個(gè)特殊接收者對(duì)象的方法承桥。
  • with 標(biāo)準(zhǔn)庫函數(shù)允許你調(diào)用同一個(gè)對(duì)象的多個(gè)方法,而不需要反復(fù)寫出這個(gè)對(duì)象的引用 根悼。apply 函數(shù)讓你使用構(gòu)建者風(fēng)格的 API 創(chuàng)建和初始化任何對(duì)象凶异。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市番挺,隨后出現(xiàn)的幾起案子唠帝,更是在濱河造成了極大的恐慌,老刑警劉巖玄柏,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件襟衰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡粪摘,警方通過查閱死者的電腦和手機(jī)瀑晒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來徘意,“玉大人苔悦,你說我怎么就攤上這事∽颠郑” “怎么了玖详?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)勤讽。 經(jīng)常有香客問我蟋座,道長(zhǎng),這世上最難降的妖魔是什么脚牍? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任向臀,我火速辦了婚禮,結(jié)果婚禮上诸狭,老公的妹妹穿的比我還像新娘券膀。我一直安慰自己,他們只是感情好驯遇,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布芹彬。 她就那樣靜靜地躺著,像睡著了一般叉庐。 火紅的嫁衣襯著肌膚如雪舒帮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音会前,去河邊找鬼。 笑死匾竿,一個(gè)胖子當(dāng)著我的面吹牛瓦宜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岭妖,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼临庇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了昵慌?” 一聲冷哼從身側(cè)響起假夺,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤靶累,失蹤者是張志新(化名)和其女友劉穎档痪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雁比,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淳蔼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年侧蘸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹉梨。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讳癌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出存皂,到底是詐尸還是另有隱情晌坤,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布旦袋,位于F島的核電站骤菠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猜憎。R本人自食惡果不足惜娩怎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胰柑。 院中可真熱鬧截亦,春花似錦、人聲如沸柬讨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踩官。三九已至却桶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颖系。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國打工嗅剖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘁扼。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓信粮,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親趁啸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子强缘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349