(轉(zhuǎn)載)原文鏈接:https://mp.weixin.qq.com/s/Rxw_oUTQv_Tr61-z16YGdw
Kotlin修煉指南(二):lambda表達(dá)式的精髓
lambda表達(dá)式是Kotlin函數(shù)式編程的一個(gè)重要概念,要想掌握函數(shù)式編程,就必須熟練掌握l(shuí)ambda表達(dá)式,并掌握它的各種寫(xiě)法和實(shí)現(xiàn),這些都是掌握函數(shù)式編程的基礎(chǔ)。
lambda基本形式
lambda表達(dá)式有三大特征:
lambda表達(dá)式存在于{}中
參數(shù)及參數(shù)類型(可省略)在->左邊
函數(shù)體在->右邊
lambda表達(dá)式返回值總是返回函數(shù)體內(nèi)部最后一行表達(dá)式的值
這三種形式的lambda表達(dá)式必須要能夠非常熟練的掌握,這樣才能進(jìn)一步的了解Kotlin和函數(shù)式編程。
無(wú)參數(shù)
無(wú)參數(shù)形式為:
val 函數(shù)名 = { 函數(shù)體 }
示例:
val hello = { println("hello kotlin") }// 等價(jià)于函數(shù)fun hello() { println("hello kotlin")}
有參數(shù)
- 完整表達(dá)方式:
val 函數(shù)名 : (參數(shù)1類型, 參數(shù)2類型, ...) -> 返回值類型 = { 參數(shù)1, 參數(shù)2, ... -> 函數(shù)體 }
- 表達(dá)式返回值類型可自動(dòng)推斷形式
val 函數(shù)名 = { 參數(shù)1:類型1, 參數(shù)2:類型2, ... -> 函數(shù)體 }
示例:
val sum: (Int, Int) -> Int = { a, b -> a + b }// 等價(jià)于val sum = { a: Int, b: Int -> a + b }// 等價(jià)于函數(shù)fun sum(a: Int, b: Int): Int { return a + b}
只有一個(gè)參數(shù)的時(shí)候,返回值中的參數(shù)形參可以省略,引用時(shí)通過(guò)it進(jìn)行引用
lambda的調(diào)用有兩種方式,一種是通過(guò)()來(lái)進(jìn)行調(diào)用,另一種是通過(guò)invoke()函數(shù)進(jìn)行調(diào)用,兩種方式?jīng)]有區(qū)別。
fun main(args: Array<String>) { val lambda = { println("test") } lambda() lambda.invoke()}
在使用lambda表達(dá)式的時(shí)候,可以用下劃線(_)表示未使用的參數(shù),表示不處理這個(gè)參數(shù)。
匿名函數(shù)
匿名函數(shù)形式為:
val 函數(shù)名 = fun(參數(shù)1:類型1, 參數(shù)2:類型2, ...): 返回值類型 { 函數(shù)體 }
示例:
val sum = fun(a: Int, b: Int): Int { return a + b}// 等價(jià)于函數(shù)fun sum(a: Int, b: Int): Int { return a + b}
高階函數(shù)的演變
所謂高階函數(shù),實(shí)際上就是數(shù)學(xué)中的復(fù)合函數(shù)的概念,f(g(x))。
引用函數(shù)
fun cal(a: Int, b: Int, f: (c: Int, d: Int) -> Int): Int { return f(a, b)}fun sum(a: Int, b: Int): Int { return a + b}fun main(args: Array<String>) { val result = cal(2, 3, ::sum) println("result = $result") // result = 8}
在cal函數(shù)中的最后一個(gè)參數(shù)是 f: (a: Int, b: Int) -> Int 表示該參數(shù)是一個(gè)函數(shù)引用,函數(shù)體內(nèi)調(diào)用了最后一個(gè)參數(shù)指向的函數(shù)。隨后定義了sum函數(shù),該函數(shù)就是cal函數(shù)的第三個(gè)參數(shù)。
::sum表示sum函數(shù)的引用,cal(2, 3, ::sum)這一句就相當(dāng)于執(zhí)行了sum(2, 3),所以輸出結(jié)果為5。
函數(shù)引用可以進(jìn)一步的簡(jiǎn)化函數(shù)的調(diào)用,類似下面這個(gè)例子:
class Test { fun doSomething() { println("test") } fun doTest(f: (Test) -> Unit) { f(this) }}fun main(args: Array<String>) { val t = Test() // 常規(guī)寫(xiě)法 傳入函數(shù) t.doTest { test -> test.doSomething() } // 使用引用函數(shù)(Test::doSomething實(shí)際上是對(duì)lambda表達(dá)式{test -> test.doSomething()}的簡(jiǎn)化) t.doTest(Test::doSomething)}
參數(shù)lambda化
fun cal(a: Int, b: Int, f: (a: Int, b: Int) -> Int): Int { return f(a, b)}fun main(args: Array<String>) { val sum = { a: Int, b: Int -> a + b } val result = cal(2, 3, sum) println("result = $result") // result = 5}
利用前面寫(xiě)的方式,將一個(gè)函數(shù)改寫(xiě)為lambda形式,作為參數(shù)直接賦值給cal函數(shù)。
那么更進(jìn)一步,可以省略這個(gè)lambda的變量,直接將lambda表達(dá)式傳入函數(shù)。
fun cal(a: Int, b: Int, f: (a: Int, b: Int) -> Int): Int { return f(a, b)}fun main(args: Array<String>) { val result = cal(2, 3, { a: Int, b: Int -> a + b }) println("result = $result") // result = 5}
另外,在Kotlin中調(diào)用高階函數(shù)時(shí),如果最后一個(gè)參數(shù)為lambda表達(dá)式,可以將lambda表達(dá)式寫(xiě)在外面,而且如果沒(méi)有其它參數(shù)的話,小括號(hào)也是可以省略的。
fun cal(a: Int, b: Int, f: (a: Int, b: Int) -> Int): Int { return f(a, b)}fun main(args: Array<String>) { val result = cal(2, 3, { a: Int, b: Int -> a + b }) // 兩種寫(xiě)法等價(jià) val result2 = cal(2, 3) { a: Int, b: Int -> a + b } println("result = $result") // result = 5}
函數(shù)變量
fun main(args: Array) { val sumLambda = {a: Int, b: Int -> a + b} var numFun: (a: Int, b: Int) -> Int numFun = {a: Int, b: Int -> a + b} numFun = sumLambda numFun = ::sum numFun(1,2)}
可以看到這個(gè)變量可以等于一個(gè)lambda表達(dá)式,也可以等于另一個(gè)lambda表達(dá)式變量,還可以等于一個(gè)普通函數(shù),但是在函數(shù)名前需要加上(::)來(lái)獲取函數(shù)引用。
lambda表達(dá)式實(shí)例
下面通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)看下一些具體的lambda表達(dá)式是怎么寫(xiě)的。
// 匿名函數(shù)val sum = fun(a: Int, b: Int): Int { return a + b}// 具名函數(shù)fun namedSum(a: Int, b: Int): Int { return a + b}// 高階函數(shù)fun highSum(a: Int, b: Int, f: (Int, Int) -> Int): Int { return f(a, b)}fun main(args: Array<String>) { // 通過(guò)()來(lái)執(zhí)行匿名函數(shù)sum val add = sum(1, 2) println(add) // 通過(guò)lambda表達(dá)式來(lái)完成函數(shù)highSum val add2 = highSum(3, 4) { a, b -> a + b } println(add2) // 通過(guò)函數(shù)引用來(lái)完成函數(shù)highSum val add3 = highSum(5, 6, ::namedSum) println(add3) // forEach參數(shù)接收一個(gè)函數(shù) args.forEach({ it: String -> println(it) }) // 去掉返回值,自動(dòng)推斷 args.forEach({ it -> println(it) }) // 只有一個(gè)參數(shù)的時(shí)候可以省略it args.forEach({ println(it) }) // lambda表達(dá)式在最后一個(gè)參數(shù)可以外移 args.forEach() { println(it) } // 函數(shù)若無(wú)參數(shù)可以去掉() args.forEach { println(it) } // 引用函數(shù) args.forEach(::println)}
函數(shù)類型與實(shí)例化
類似Int、String這樣的數(shù)據(jù)類型,函數(shù)的類型表示為:
(Type1, Type2, ...) -> Type// 例如(Int) -> Int// 所以才有了這樣的函數(shù)fun test(a: Int, f: (Int) -> Int): Int { return f(a)}
其中(Int) -> Int的地位和Int、String的地位是等價(jià)的。
函數(shù)既然是一種類型,那么函數(shù)也和Int、String一樣,是具有可實(shí)例化的實(shí)例的,例如Int的實(shí)例1、String的實(shí)例“xys”,那么獲取函數(shù)的實(shí)例,主要客源通過(guò)下面三種方式:
:: 雙冒號(hào)操作符表示對(duì)函數(shù)的引用
lambda表達(dá)式
匿名函數(shù)
fun main(args: Array<String>) { // 引用函數(shù) println(test(1, 2, ::add)) // 匿名函數(shù) val add = fun(a: Int, b: Int): Int { return a + b } println(test(3, 4, add)) // lambda表達(dá)式 println(test(5, 6, { a, b -> a + b }))// lambda作為最后一個(gè)參數(shù)可以提到括號(hào)外 println(test(5, 6) { a, b -> a + b })}fun test(a: Int, b: Int, f: (Int, Int) -> Int): Int { return f(a, b)}fun add(a: Int, b: Int): Int { return a + b}
lambda表達(dá)式的類型
通過(guò)下面的例子,可以了解下lambda表達(dá)式的類型,代碼如下所示。
// 無(wú)參,返回String() -> String// 兩個(gè)整型參數(shù),返回字符串類型(Int, Int) -> String// 傳入了一個(gè)lambda表達(dá)式和一個(gè)整型,返回Int(()->Unit, Int) -> Int
開(kāi)發(fā)者可以通過(guò)類似上面的形式來(lái)表達(dá)lambda表達(dá)式的類型,不過(guò)和Int、String一樣,lambda表達(dá)式也有自己的類,即Function類。
Kotlin封裝了Function0到Function22,一共23個(gè)Function類型,分別表示參數(shù)個(gè)數(shù)從0到22。
lambda表達(dá)式的return
除非使用標(biāo)簽指定了返回點(diǎn),否則return從最近的使用fun關(guān)鍵字聲明的函數(shù)返回。
fun main(args: Array<String>) { var sum: (Int) -> Unit = tag@{ print("Test return $it") return@tag } sum(3)}
SAM轉(zhuǎn)換
SAM = Single Abstract Method,即唯一抽象方法
SAM轉(zhuǎn)換是為了在Kotlin代碼中調(diào)用Java代碼所提供的一個(gè)語(yǔ)法糖,即為Java的單一方法的接口,提供lambda形式的實(shí)現(xiàn),例如Android中最常見(jiàn)的view.setOnClickListener:
// SAM convert in KTview.setOnClickListener{ view -> doSomething}// Java接口public interface OnClickListener { void onClick(View v);}
SAM轉(zhuǎn)換是專門(mén)為Java提供的語(yǔ)法糖,用于將lambda表達(dá)式轉(zhuǎn)換成相應(yīng)的匿名類的實(shí)例。在Kotlin中實(shí)現(xiàn)相同的功能,只需要使用函數(shù)參數(shù)即可。
帶接收者的lambda表達(dá)式
lambda表達(dá)式實(shí)際上有兩種形式,一種是前面介紹的基本形式,還有一種是帶接收者的形式,兩種lambda表達(dá)式如下所示。
普通lambda表達(dá)式:{ () -> R }
即函數(shù)無(wú)入?yún)?,返回值為R類型。
帶接收者的lambda表達(dá)式:{ T.() -> R }
即申明一個(gè)T類型的接收者對(duì)象,且無(wú)入?yún)?,返回值為R類型。
Kotlin中的拓展函數(shù),實(shí)際上就是使用的帶接收者的lambda表達(dá)式,
帶接收者的lambda與普通的lambda的區(qū)別主要在于this的指向區(qū)別,T.() -> R里的this代表的是T的自身實(shí)例,而() -> R里,this代表的是外部類的實(shí)例。
使用typealias給重復(fù)申明的lambda表達(dá)式設(shè)置別名
fun fun1(f: (Int) -> Unit) { f(1)}fun fun2(f: (Int) -> Unit) { f(2)}// 使用typealiastypealias intFun = (Int) -> Unitfun fun3(f: intFun) { f(3)}fun fun4(f: intFun) { f(4)}fun main(args: Array<String>) { fun1 { println(it) } fun2 { println(it) } fun3 { println(it) } fun4 { println(it) }}
閉包
如果一個(gè)函數(shù)內(nèi)部申明或者返回了一個(gè)函數(shù),那么這個(gè)函數(shù)被稱之為閉包。
函數(shù)內(nèi)部的變量可以被函數(shù)內(nèi)部申明的函數(shù)所訪問(wèn)、修改,這就讓閉包可以攜帶狀態(tài)(所有的中間值都會(huì)被放入內(nèi)存中)。
開(kāi)發(fā)者可以通過(guò)閉包讓函數(shù)具有狀態(tài),從而可以封裝函數(shù)的狀態(tài),讓函數(shù)具有面向?qū)ο蟮奶匦浴?/p>
為什么需要閉包
在了解閉包之前,需要先了解下變量的作用域,在kotlin中,變量的作用域只有兩種,即全局變量和局部變量。
全局變量,函數(shù)內(nèi)部和函數(shù)外部均可以直接訪問(wèn)。
局部變量,只有函數(shù)內(nèi)部可以訪問(wèn)。
那么如何在函數(shù)外部訪問(wèn)函數(shù)內(nèi)部的局部變量呢,這就需要通過(guò)閉包來(lái)進(jìn)行訪問(wèn),閉包的設(shè)計(jì)就是為了能讓開(kāi)發(fā)者讀取某個(gè)函數(shù)內(nèi)部的變量。
所以閉包就是能夠讀取其它函數(shù)的局部變量的函數(shù)。
閉包讓函數(shù)攜帶狀態(tài)
fun test(): () -> Int { var a = 1 println(a) return fun(): Int { a++ println(a) return a }}fun main(args: Array<String>) { val t = test() t() t()}// output123
變量t的類型實(shí)際上是一個(gè)匿名函數(shù),所以在調(diào)用t函數(shù)執(zhí)行的時(shí)候,實(shí)際上執(zhí)行的是返回的匿名函數(shù),同時(shí),由于閉包可以攜帶外包的變量值,所以a的狀態(tài)值被傳遞了下來(lái)。
閉包可以訪問(wèn)函數(shù)體之外的變量,這被稱之為變量捕獲。閉包會(huì)將捕獲的變量保存在一個(gè)特殊的內(nèi)存區(qū)域,從而實(shí)現(xiàn)閉包攜帶狀態(tài)的功能。
kotlin實(shí)現(xiàn)接口回調(diào)
單方法回調(diào)
class Test { private var callBack: ((str: String) -> Unit)? = null fun setCallback(myCallBack: ((str: String) -> Unit)) { this.callBack = myCallBack }}
使用函數(shù)替代了接口的實(shí)現(xiàn)。
回調(diào)寫(xiě)法的演進(jìn)
Java思想的kotlin寫(xiě)法
interface ICallback { fun onSuccess(msg: String) fun onFail(msg: String)}class TestCallback { var myCallback: ICallback? = null fun setCallback(callback: ICallback) { myCallback = callback } fun init() { myCallback?.onSuccess("success message") }}fun main(args: Array<String>) { val testCallback = TestCallback() testCallback.setCallback(object : ICallback { override fun onSuccess(msg: String) { println("success $msg") } override fun onFail(msg: String) { println("fail $msg") } }) testCallback.init()}
使用lambda表達(dá)式替代匿名內(nèi)部類實(shí)現(xiàn)。
class TestCallback { var mySuccessCallback: (String) -> Unit? = {} var myFailCallback: (String) -> Unit? = {} fun setCallback(successCallback: (String) -> Unit, failCallback: (String) -> Unit) { mySuccessCallback = successCallback myFailCallback = failCallback } fun init() { mySuccessCallback("success message") myFailCallback("fail message") }}fun main(args: Array<String>) { val testCallback = TestCallback() testCallback.setCallback({ println("success $it") }, { println("fail $it") }) testCallback.init()}
這樣去掉了接口和匿名內(nèi)部類。
高階函數(shù)的使用場(chǎng)景
高階函數(shù)的一個(gè)重要使用場(chǎng)景就是集合的操作,里面下面這個(gè)例子,分別使用Java和Kotlin實(shí)現(xiàn)了「找最大值」的方法。
data class Test(val name: String, val age: Int)fun main(args: Array<String>) { // Java寫(xiě)法 val testList = listOf(Test("xys", 18), Test("qwe", 12), Test("rty", 10), Test("zxc", 2)) findMax(testList) // Kotlin寫(xiě)法 println(testList.maxBy { it.age }) println(testList.maxBy(Test::age))}fun findMax(test: List<Test>) { var max = 0 var currentMax: Test? = null for (t in test) { if (t.age > max) { max = t.age currentMax = t } } println(currentMax)}
函數(shù)式集合操作
fliter & map
filter用于數(shù)據(jù)的篩選,類似的還有filterIndexed,即帶Index的過(guò)濾器、filterNot,即過(guò)濾所有不滿足條件的數(shù)據(jù)。
map用于對(duì)數(shù)據(jù)進(jìn)行變換,代表了一種一對(duì)一的變換關(guān)系,它可以對(duì)集合中的數(shù)據(jù)做一次變換,類似的還有mapIndexed()。
fun main(args: Array<String>) { val test = listOf(1, 3, 5, 7, 9) // filter函數(shù)遍歷集合并選出應(yīng)用給定lambda后會(huì)返回true的那些元素 println("大于5的數(shù) ${test.filter { it > 5 }}") // map函數(shù)對(duì)集合中的每一個(gè)元素應(yīng)用給定的函數(shù)并把結(jié)果收集到一個(gè)新集合 println("平方操作 ${test.map { it * it }}") val testList = listOf(Test("xys", 18), Test("qwe", 12), Test("rty", 10), Test("zxc", 2)) // 將一個(gè)列表轉(zhuǎn)換為另一個(gè)列表 println("只展示name ${testList.map { it.name }}") // filter與map鏈?zhǔn)讲僮? println("展示age大于10的name ${testList.filter { it.age > 10 }.map { it.name }}")}data class Test(val name: String, val age: Int)
all & any & count & find
fun main(args: Array<String>) { val test = listOf(1, 3, 5, 7, 9) // all判斷是否全部符合lambda表達(dá)式的條件 println("是否全部符合>10 ${test.all { it > 10 }}") // any判斷是否存在有符合lambda表達(dá)式的條件的數(shù)據(jù) println("是否存在>8 ${test.any { it > 8 }}") // count獲取符合lambda表達(dá)式條件的數(shù)據(jù)個(gè)數(shù) println("大于5的個(gè)數(shù) ${test.count { it > 5 }}") // find獲取符合lambda表達(dá)式條件的第一個(gè)數(shù)據(jù) println("第一個(gè)大于5 ${test.find { it > 5 }}") println("最后一個(gè)大于5 ${test.findLast { it > 5 }}")}
groupBy & partition & flatMap
flatMap()代表了一個(gè)一對(duì)多的關(guān)系,可以將每個(gè)元素變換為一個(gè)新的集合,再將其平鋪成一個(gè)集合。
groupBy()方法會(huì)返回一個(gè)Map<k,list>的Map對(duì)象,其中Key就是我們分組的條件,value就是分組后的集合。</k,list
fun main(args: Array<String>) { val test = listOf("a", "ab", "b", "bc") // groupBy按照l(shuí)ambda表達(dá)式的條件重組數(shù)據(jù)并分組 println("按首字母分組 ${test.groupBy(String::first)}") // partition按照條件進(jìn)行分組,該條件只支持Boolean類型條件,first為滿足條件的,second為不滿足的 test.partition { it.length > 1 }.first.forEach { print("$it、") } println() test.partition { it.length > 1 }.second.forEach { print("$it、") } println() // flatMap首先按照l(shuí)ambda表達(dá)式對(duì)元素進(jìn)行變換,再將變換后的列表合并成一個(gè)新列表 println(test.flatMap { it.toList() })}
sortedBy
sortedBy()用于根據(jù)指定的規(guī)則進(jìn)行順序排序,如果要降序排序,則需要使用sortedByDescending(),
fun main(args: Array<String>) { val test = listOf(3, 2, 4, 6, 7, 1) println(test.sortedBy { it })}
take & slice
take()和slice()用于進(jìn)行數(shù)據(jù)切片,從某個(gè)集合中返回指定條件的新集合。類似的還有takeLast()、takeIf()等。
fun main(args: Array<String>) { val test = listOf(3, 2, 4, 6, 7, 1) // 獲取前3個(gè)元素的新切片 println(test.take(3)) // 獲取指定index組成的新切片 println(test.slice(IntRange(2, 4)))}
reduce
fun main(args: Array<String>) { val test = listOf("a", "ab", "b", "bc") // reduce函數(shù)將一個(gè)集合的所有元素通過(guò)傳入的操作函數(shù)實(shí)現(xiàn)數(shù)據(jù)集合的累積操作效果。 println(test.reduce { acc, name -> "$acc$name" })}
作用域函數(shù)
前面文章提到的作用域函數(shù)就是高階函數(shù)的一個(gè)重要實(shí)例。
lambda表達(dá)式的其它特性
惰性序列操作
當(dāng)一些集合函數(shù)進(jìn)行鏈?zhǔn)秸{(diào)用的時(shí)候,每個(gè)函數(shù)的調(diào)用結(jié)果都將保存為一個(gè)新的臨時(shí)列表,因此,大量的鏈?zhǔn)讲僮鲿?huì)產(chǎn)生大量的中間變量,從而導(dǎo)致性能問(wèn)題,為了提高效率,可以把鏈?zhǔn)讲僮鞲臑樾蛄校╯equance)。
調(diào)用擴(kuò)展函數(shù)asSequence把任意集合轉(zhuǎn)換成序列,調(diào)用toList來(lái)做反向的轉(zhuǎn)換
fun main(args: Array<String>) { val testList = listOf(Test("xys", 18), Test("qwe", 12), Test("rty", 10), Test("zxc", 2)) // 函數(shù)的鏈?zhǔn)秸{(diào)用 println("集合調(diào)用 展示age大于10的name ${ testList.filter { it.age > 10 } .map { it.name }}") // 函數(shù)的序列操作 println("序列操作 展示age大于10的name ${ testList.asSequence() .filter { it.age > 10 } .map { it.name } .toList()}")}data class Test(val name: String, val age: Int)
一個(gè)完整的序列包括兩個(gè)操作,即中間序列和末端序列,中間序列操作始終都是惰性的,末端序列操作觸發(fā)所有的惰性計(jì)算。
//中間操作 //末端操作testList.asSequence(). filter {..}.map {..}.toList()
因此,通過(guò)序列的這種方式,就避免了產(chǎn)生大量中間集合,從而提高了性能。
延遲計(jì)算
fun main(args: Array<String>) { val addResult = lateAdd(2, 4) print(addResult())}fun lateAdd(a: Int, b: Int): Function0<Int> { fun add(): Int { return a + b } return ::add}
在lateAdd內(nèi)部定義了一個(gè)局部函數(shù),最后返回了該局部函數(shù)的引用,對(duì)結(jié)果使用()操作符拿到最終的結(jié)果,達(dá)到延遲計(jì)算的目的。
fun main(args: Array<String>) { val funs = mapOf("sum" to ::sum) val mapFun = funs["sum"] if (mapFun != null) { val result = mapFun(1, 2) println("sum result -> $result") }}fun sum(a: Int, b: Int): Int { return a + b}
掌握了lambda表達(dá)式,就等于戰(zhàn)士有了槍,多練習(xí),多思考,才能掌握l(shuí)ambda表達(dá)式的精髓,為后面掌握函數(shù)式編程,打下堅(jiān)實(shí)的基礎(chǔ)。