lambda表达式详解)
1 lambda表达式本质
《kotlin实战》中说过,lambda表达式是可以作为函数参数的代码块,你可以将其用在任何需要储存和传递一段行为的地方。说白了就是传递函数。在java中虽然不允许我们传递一个函数,但我们依然可以通过匿名内部类实现接口来达到同样的效果。在kotlin中增加了函数类型,让我们可以把函数当做值来对待,可以直接传递函数
1.1 kotlin中的函数类型
//定义一个具有函数类型参数的函数,让我们可以动态的将整数转换成字符串类型
fun intToString(num: Int, n :(Int) -> String): String{
return n(num)
}
fun main() {
// 传递一个匿名函数作为参数
//为了方便就直接将整数转换成字符串,实际上可以根据整数的值换成不同的字符串
print(intToString(4, fun (num: Int): String{
return num.toString()
}))
//将匿名函数简写成lambda表达式
print(intToString(4, { num: Int -> num.toString() }))
//由于传递的函数只有一个参数,可以被自动推断出来,所以可以省略类型
print(intToString(4, {num -> num.toString()}))
//it是kotlin提供的默认参数,当你没有显式定义实参名称时会生成
print(intToString(4, {it.toString()}))
//如果lambda表达式是最后一个实参,可以被放到括号外边
print(intToString(4){it.toString()})
//根据整数的值转换字符串
print(intToString(4, { num: Int -> if(num > 4) num.toString() else "null" }))
}
可以发现相比于java中先继承一个匿名内部类,然后重写里面的函数的实现方式,kotlin使用函数类型要简单的多。lambda表达式会自动地将最后一个表达式的值返回,所以不需要我们写出return,而匿名函数依然需要写return。
1.2 将函数赋值给一个变量
前面说过kotlin中可以把函数当成一个值来传递,我们同样也可以把函数赋值给一个变量。赋值之后我们可以通过这个变量来调用这个函数
fun a(str: String){
print(str)
}
fun main() {
//::运算符可以把一个已有的函数转换成一个值,这就是成员引用
val value = ::a
value("hello, world!")
//将匿名函数赋值给变量
val anonymous = fun (str: String){
print(str)
}
anonymous("hello, world!")
//使用lambda表达式
val lambda = { str: String -> print(str)}
lambda("hello, world!")
}
1.3 lambda表达式、匿名函数和成员引用实现原理
将上面代码转成java可以发现,lambda表达式、匿名函数还有成员引用都被转换成了对象。在Functions.kt中可以看到23个接口,名字从function0到function22,数字代表了参数的数量,而这些接口都实现了Function接口。所以我们使用的lambda表达式、匿名函数和成员引用都根据参数的数量被转化成了我们看不见的匿名类对象,调用时,实际上调用的是这些对象的invoke方法。
注意,从编译器的角度看,lambda是一个代码块,不是一个对象,所以lambda内部没用匿名对象那样的this,lambda内部的this引用指向的是lambda外部的类。
2 lambda使用
2.1 lambda捕捉
与java不同,kotlin允许在lambda内部访问非final变量甚至修改它们。从lambda内访问外部变量,称为lambda捕捉。
默认情况下,局部变量的生命周期被限制在声明这个变量的函数中。但是如果它被lambda捕捉了,使用这个变量的代码可以被存储并稍后在执行。
对于非final变量来说,它的值被封装在一个特殊的包装器中,这样你就可以改变这个值,而这个包装器的引用会和lambda一起被存储。
需要注意的是当lambda被用作事件处理器或者用在其他异步执行的情况,对局部变量的修改之后在lambda执行的时候发生。
2.2 成员引用
val getAge = Person::age
这种表达式被称为成员引用,它提供简明语法来创建一个调用单个方法或者访问单个属性的函数值。
class Person(var age: Int){
}
fun main() {
val getAge = Person::age // 与{person: Person -> person.age}等效
val jack = Person(24)
print(getAge(jack))
}
还可以用构造方法引用存储或延期执行创建类实例的动作:
data class Person(val name: String, val age: Int)
val creationPerson = ::Person
val p = creationPerson("jack", 29) //此处才真正创建实例
2.3 集合的函数式API
函数 | 作用 | 例子 |
---|---|---|
filter | filter函数遍历集合并选出应用给定lambda后会返回true的那些元素 | persons.filter{it.age > 18} 选出年龄大于18的人 |
map | map函数对集合中的每一个元素应用给定的函数并把结果收集到一个新集合 | persons.map{it.age}返回一个所有人的年龄集合 |
all | 检查集合中元素是不是全都满足某个条件,返回布尔值 | persons.all{it.age > 18} 判断是不是所有人的年龄都大于18 |
any | 判断集合中是否有某个对象满足条件,返回布尔值 | persons.any{it.age > 18} 判断有没有人的年龄大于18 |
count | 计算集合中满足条件的对象数量 | persons.coun{it.age > 18} 计算年龄大于18的人的数量 |
find | 返回第一个符合条件的对象 | persons.find{ it.age > 18} 返回第一个年龄大于18的人 |
groupBy | 把列表转换为分组的map | persons.groupBy{ it.age} 返回一个map,key是年龄,value的对应年龄的人的列表 |
flatMap | 处理嵌套集合,将嵌套集合平铺 | class book(val authors: List); books.flatMap(it.authors} 获取所有书中的所有作者。 |
flatten | 不需要变换,只是平铺嵌套集合 | listOfLists.flatten() |
2.4 带接受者的lambda: “with” 和 “apply”
构建字母表:
fun alphabet() = with(StringBuilder()){
for(letter in 'A'..'Z'){
append(letter)
}
append("\nNow I know the alphabet")
toString()
}
apply 会将对象返回:
val string = StringBuilder().apply{
for(letter in 'A'..'Z'){
append(letter)
}
append("\nNow I know the alphabet")
}.toString()