總述
lambda 編譯后生成的類都繼承 Lambda 類缝裁,并根據(jù)參數(shù)個(gè)數(shù)實(shí)現(xiàn) FunctionN 接口 —— N 表示參數(shù)個(gè)數(shù)嘹黔,所以 lambda 的實(shí)際類型是 FunctionN滚婉。
-
屬性成員引用的實(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á)式的變量,但反之不行
- 方法成員引用的實(shí)際類型是 KFunctionN卧须,但編譯后生成的類實(shí)現(xiàn)了函數(shù)類型接口另绩,因此方法成員引用可直接賦值給初始值為 lambda 的變量儒陨,但反之不行:
lambda 是函數(shù)類型的實(shí)例,成員引用是函數(shù)類型的子類的實(shí)例笋籽。因此成員引用可直接賦值給 lambda蹦漠,但反之不行。
函數(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ǔ)法
- 始終用花括號(hào)包圍。參數(shù)列表不需要用括號(hào)括起來(lái)竭贩。使用箭頭把參數(shù)列表與函數(shù)體分開(kāi)蚜印。
如圖:
- lambda 表達(dá)式中可以含有多個(gè)表達(dá)式,只有最后表達(dá)式的結(jié)果是 lambda 的返回值留量。
使用
-
可以直接賦值給一個(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")}() }
-
如果表達(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 }) }
-
如果能推導(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 })
-
如果不需要參數(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)原理
當(dāng)表達(dá)式捕獲 final 變量時(shí)做院,變量的值會(huì)被復(fù)制下來(lái),和使用這個(gè)值的表達(dá)式一起存儲(chǔ)濒持。java 中也是這個(gè)原理键耕。
-
對(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)
其格式如下:
無(wú)論 member 是函數(shù)還是屬性堕汞,其后都不加 ()
如果是頂層函數(shù)、屬性囤捻,則省略 Class 臼朗,直接以 :: 開(kāi)頭。
如果引用的是構(gòu)造函數(shù)蝎土,則寫成 ::類名视哑。
成員引用一樣適用于擴(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 屬性的值耗绿。
kt 1.0 中苹支,執(zhí)行成員引用時(shí),始終需要提供一個(gè)該類的實(shí)例误阻。
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ì)象零院。
-
接收者可以當(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()) }
將函數(shù)參數(shù)中的一個(gè)參數(shù)類型移到括號(hào)外面,并使用 . 將它與其余參數(shù)區(qū)分開(kāi)告抄。
- 由擴(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)
}