总述
lambda 编译后生成的类都继承 Lambda 类,并根据参数个数实现 FunctionN 接口 —— N 表示参数个数,所以 lambda 的实际类型是 FunctionN。
-
属性成员引用的实际类型是 PropertyReference1 子类,而后者实现了 KProperty1 ,KProperty1 定义如下:
public interface KProperty1<T, out R> : KProperty<R>, (T) -> R
由于 PropertyRefrence1 外界无法使用,所以可以认为 属性成员引用的实质类型是 KProperty1。
因此,属性成员可直接赋值给初始值为 lambda 表达式的变量,但反之不行
- 方法成员引用的实际类型是 KFunctionN,但编译后生成的类实现了函数类型接口,因此方法成员引用可直接赋值给初始值为 lambda 的变量,但反之不行:
lambda 是函数类型的实例,成员引用是函数类型的子类的实例。因此成员引用可直接赋值给 lambda,但反之不行。
函数类型变量的运行其实质就是运行其内部的 invoke 方法 —— 参考 invoke 约定。
lambda
lambda 表达式的本质是 可以传递给其他函数的小段代码
lambda 是函数类型的实例,因此可以直接赋值给函数类型的变量。
每一个 lambda 都会被编译成一个匿名内部类,传入 lambda 的地方就是传入一个该类的实例。
fun test(id:String){
Thread{ // 使用 lambda
println("runnable")
}.start()
}
// 创建一个内部类
class Test$1 : Runnable{
override fun run() {
println("runnable")
}
}
fun test(id:String){
Thread(Test$1()).start()// 实际上是传入了一个内部类的实例
}
语法
- 始终用花括号包围。参数列表不需要用括号括起来。使用箭头把参数列表与函数体分开。
如图:
- lambda 表达式中可以含有多个表达式,只有最后表达式的结果是 lambda 的返回值。
使用
-
可以直接赋值给一个变量,然后将这个变量当作普通函数;或者直接运行表达式
fun main(args: Array<String>) { val l = {x:Int,y:Int -> x+y} // 此处要加分号。不然会认为下个表达式是 println() 的一个参数 println(l(2,3)); { println("xx")}() }
-
如果表达式是最后一个参数,可以将表达式放到括号外面;如果只有表达式一个参数,可以省略小括号。如果要传递两个或更多的表达式,不能将表达式定义在小括号外面。
fun main(args: Array<String>) { val list = listOf(Person(1), Person(3), Person(2)) // 正常传递表达式 println(list.maxBy({ p: Person -> p.age })) // 将表达式提取到括号外面 println(list.maxBy() {p: Person -> p.age }) // 只有一个参数,省略小括号 println(list.maxBy { p: Person -> p.age }) }
-
如果能推导出参数类型,则可以省略表达式中的参数类型;如果只有一个参数,且类型可以推导出类型,可以使用 it 指代参数,连参数名都不用写,直接使用 it。接上例,可以再简化:
// 能推导出类型,则省略参数类型 println(list.maxBy { person -> person.age }) // 能推导出类型,且只有一个参数,则可以省略参数名使用 it 指代 println(list.maxBy { it.age })
-
如果不需要参数,可以省略 -> ,直接写表达式的执行语句:
fun main(args: Array<String>) { var a = 10 val f = { println("$a") } }
访问变量
与对象表达式一样,表达式可以访问这个函数的参数,以及定义在表达式之前的局部变量。
kt 中允许表达式访问非 final 变量(通过 var 声明的变量),并修改这些变量。
fun test(name: String){
var age = 10
// 可以访问参数 name,也可以访问定义在表达式前的 age ,但不能访问 sex
{ println("${name} ${age}")}()
var sex = "f"
}
实现原理
当表达式捕获 final 变量时,变量的值会被复制下来,和使用这个值的表达式一起存储。java 中也是这个原理。
-
对于非 final 变量,它的值会被封装在一个特殊的包装器中,将包装器的引用和表达式一起存储。在整个过程中,引用不会发生变化,但包装器中的属性可以改变。
// 包装器 class Ref<T>(var value:T) fun test(name: String){ val age = 10 val ref = Ref(age) val lambda = {ref.value++} }
与表达式一起存储的是 ref,ref 在整个过程中都不会发生变化,但其属性 value 却可以改变。
成员引用
它提供了一个简明语法,来创建一个调用单个方法或者访问单个属性的函数。
成员引用在编译后会生成一个内部类的实例(该内部类有 invoke 方法),执行成员引用就相当于执行 invoke 方法,而 invoke 方法内部会调用引用的函数(函数成员引用时)或者访问指定的属性(属性成员引用时)。
相较于普通的直接调用方法和引用属性,成员引用是一个函数类型的实例,因此可以将成员引用作为实参传递给函数类型的形参:
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() 相当于执行成员引用,所以会获取到对应的属性值
}
class Obj(val a: Int)
其格式如下:
无论 member 是函数还是属性,其后都不加 ()
如果是顶层函数、属性,则省略 Class ,直接以 :: 开头。
如果引用的是构造函数,则写成 ::类名。
成员引用一样适用于扩展函数、属性。
使用
成员引用会创建一个函数,执行该函数时,会执行指定的方法或访问指定的属性。如下例,main 函数中的 get 与 age 都是函数,执行 get 时会执行 person.get() ,而执行 age 时会获取 person 中的 age 属性的值。
kt 1.0 中,执行成员引用时,始终需要提供一个该类的实例。
kt 1.1 中,可以直接使用使用对象进行定义,如下例中的 age1 与 get1 的定义方式。在执行时,不需要再传入实例对象。
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 是互通的,可以互相使用
无论是成员引用还是 lambda ,其实现时都会转成 Function 的实例。因此,它们是同一类型,可以相互赋值 —— 只要泛型一致即可。如下,所有的输出都是 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() {}
}
如定义一个 test() 函数接收一个表达式。可以直接传入一个表达式,也可以传入一个成员引用。
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
}
// 该方法接收一个 lambda 表达式
fun test(p: Person, a: (Person) -> Int) = println(a(p))
可以发现上述的表达式很简单,直接调用了 Person 的 get 方法。这种情况下,使用成员引用更方便。
当需要定义的表达式的功能已经被别的方法实现过,但使用表达式的函数只接收表达式时,可以使用成员引用。如上例中的 test 方法,它接收表达式,而表达式要实现的功能是 Person#get() 方法已经实现过的,所以直接使用成员引用即可。
lambda 管理资源
常见模式是:先获取一个资源,完成一个操作后,关闭该资源。一般在 try 中获取资源,将操作封装成 lambda 表达式,然后在 finally 中关闭资源。
如下:首先自动加锁,然后执行操作,操作执行完成后释放锁。这样封装了加锁、释放锁的逻辑,调用者只关注自己要完成的操作。
fun <T> Lock.withLock(action: () -> T): T {
lock()
try {
return action()
} finally {
unlock()
}
}
带接收者的 lambda
lambda 表达式执行时,this 指向接收者对象。因此,凡是调用接收者中的方法,都可以直接调用
首先看一般情况下的 lambda 的定义:
```kotlin
fun main(args: Array<String>) {
test{
it.append("xxxx")
}
}
fun test(b: (StringBuilder) -> Unit) {
val sb = StringBuilder()
b(sb)
println(sb.toString())
}
```
调用 test 传入的表达式中,想要调用 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())
}
可以直接调用 append() ,而不需要使用 it.append()。
上例中,StringBuilder 是接收者类型,test() 中定义的 sb 就是接收者对象。
-
接收者可以当作参数传递到表达式中,也可以直接使用接收者点的表达式名的形式进行调用:
fun buildStr(action: StringBuilder.() -> Unit) { val sb = StringBuilder() action(sb) sb.action() println(sb.toString()) }
将函数参数中的一个参数类型移到括号外面,并使用 . 将它与其余参数区分开。
- 由扩展函数类似,当函数或 lambda 被调用时需要提供这个对象,它在函数体内是可用的。
lambda 转成类
如果一个 lambda 太复杂,可以将 lambda 转成实现了函数类型接口的的类,并重写其 invoke 方法
这种方法的优点时:从 lambda 体中抽取的函数的作用域尽可能的小,它仅在判断式内部可见:
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 要的是一个函数类型的实例,所以此处可以传入 lambda 表达式或者一个函数类型的子类 —— 本例中使用的是后者。
在 Test 的类中,invoke 会被 filter 调用,而 invoke 中又调用了 isImportant 方法 —— 这就是比 lambda 要方便的地方,可以在类中定义方法。
lambda 中的 return
只有调用 lambda 的函数是 inline 函数,才能在 lambda 中使用 return
lambda 中使用 return 时,会直接结束使用 lambda 的函数,而不是只结束 lambda 表达式。可以在表达式外层套一个 run 函数,return 时只返回 run 标签。如下述代码只会输出 run ,不会输出 test。因为第二次执行 test 时,调用了 return,会直接结束掉 main 方法。第一次只是结束掉 run 方法,所以还会执行下面的语句:
fun main(args: Array<String>) {
run {
test {
return@run
}
}
println("run")
test {
return
}
println("test")
}
inline fun test(t: (Int) -> Unit) {
t(22)
}