- 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ì)象凶异。