kotlin中的lambda表达式
#### [kotlin官方文档 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####
lambda即lambda表达式,简称lambda。本质上是可以传递给其它函数的一小段代码。有了lambda,可以轻松地把通用代码结构抽取成库函数。lambda最常见的用途是和集合一起配合。kotlin甚至还拥有带接收者的lambda,这是一种特殊的lambda。
lambda的表达式和成员引用
lambda简介:作为函数参数的代码块
代码中存储和传递一小段行为是常有的任务。在老版本Java中很多需要匿名内部类来实现(java8也引入了lambda,大为改观),语法太过啰嗦。
函数式编程提供了另外一种解决方案:把函数当作值来对待。可以直接传递函数,而不需要先声明一个类再传递这个类的实例。使用lamdba表达式不仅会使代码更简洁,并且可以高效地直接传递代码快作为函数参数。
笔者注,后文我会省略大段的lambda理论文字的介绍,因为在我的认知中,接触了java8(尤其是Android)的一定对lambda有一定的认知,如果没有请自行查阅资料。
//点击监听 java lambda
btn.setOnClickListener(view ->...);
//点击监听 kotlin lambda
btn.setOnClickListener { ... }
kotlin的lambda相对于java8的lambda语法更简洁,事实上它们做的事情是一样的,都是替代了匿名内部类对象。
良好的编程风格主要原则之一就是避免代码中的任何重复。kotlin的lambda可以帮助避免代码重复(因为对集合执行通常遵循通用模式)。
//建立数据类Person作为数据源
data class Person(val name: String, val age: Int) {
}
假设需求为找到列表年龄最大的人,平时我们打代码可能是这样的。
data class Person(val name: String, val age: Int) {
}
//未使用lambda表达式
fun findTheOldest(people:List<Person>){
var maxAge=0 //存储最大年龄
var theOldest:Person?=null //存储年龄最大的人
for (person in people){
if (person.age>maxAge){ //循环赋值比现在年龄大的改变最大值
maxAge=person.age
theOldest=person
}
}
println(theOldest)
}
//数据源
val people = listOf(Person("jack", 29),
Person("nick", 23),
Person("jone", 26))
findTheOldest(people)//Person(name=jack, age=29)
如果我们使用lambda表达式,我对门的代码直接可以简化成下面的样子
class Person(var name: String, var age: Int) {
}
val people = listOf(
Person("小明", 12),
Person("小红", 12),
Person("小花", 12),
Person("小美", 12),
Person("小帅", 12)
)
println(people.maxBy { it.age % 2 }!!.name) //小帅
maxby函数可以在任何集合上调用,且只需要一个实参:一个函数,指定比较哪个值来找到最大元素。{it.age}就是实现了这个逻辑的lambda。它接收一个集合中元素作为实参(it引用)并且返回用来比较的值。如上述代码中,集合元素是Person对象,用来比较的是存储在其age属性中的年龄。
如果lambda刚好是函数或者属性委托,可以用成员引用替换。
val people = listOf(
Person("小明", 12),
Person("小红", 14),
Person("小花", 20),
Person("小美", 10),
Person("小帅", 13)
)
//注意两者的区别这种传递属性值的方式应当使用在括号里面
println(people.maxBy(Person::age)!!.name) //小花
主使用成员引用的方式传递闭包(代码块)的时候仅可以传递属性值
lambda表达式语法
如前所述(更多详情自行查阅资料或参 见初识lambda) lambda把一小段行为进行编码,既能当作值传递又能独立声明到一个变量中存储。
var sum = { x: Int, y: Int -> x + y }
println(sum(1, 6)) //7
也可以无意义的直接调用lambda表达式
//直接调用无意义(等同于直接执行lambda具体代码)
{ println(42)}()//42
正确姿势应该使用kotlin库函数run。
//正确姿势un调用
run { println(42) }//42
回到people的例子,不用任何简明语法来重写。
//未简化的标注lambda
people.maxBy({ P:Person ->P.age} )
kotlin有一种语法约定,如果lambda表达式是函数调用的最后一个实参,它可以放到括号外面。
//lambda是函数调用的最后一个实参,可以放到()外
people.maxBy( ){ P:Person ->P.age}
当lambda是函数唯一实参时,还可以去掉()
//lambda是函数唯一实参,可以省略()
people.maxBy{ P:Person ->P.age}
三种语法语义完全一样,但是最后一种更易读。但是当lamdba有两个或多个实参时,不能把超过一个的lambda放到外面,推荐使用常规语法。
把当然,lambda也能作为命名实参传递
val people = listOf(
Person("小明", 12),
Person("小红", 14),
Person("小花", 20),
Person("小美", 10),
Person("小帅", 13)
)
var str = people.joinToString(separator = " ", transform = { people: Person -> people.name })
println(str) // 小明 小红 小花 小美 小帅
因为只有一个实参,所以可以放在括号外
var str = people.joinToString(separator = " ") { people: Person -> people.name }
println(str) // 小明 小红 小花 小美 小帅
甚至可以根据类型推导特性而移除参数类型。
//显示的写出参数推导类型
var str = people.joinToString(separator = " ") { people: Person -> people.name }
//推导出参数类型
var str = people.joinToString(separator = " ") { people -> people.name }
println(str)
类型推导与局部变量一样,如果能成功被推导,就不需要显示的指定。
使用默认参数名称(注意)
//使用默认参数名称
people.maxBy { it.age} //"it"是自动生成的参数名称
默认名称it只会在实参名称没有显示的指定时候才会生成。it能大大缩短简化代码,但是不应该滥用,尤其是在lambda嵌套情况下,最好显示声明lambda参数。否则很难搞清it引用的到底是哪个值,本末倒置。
如果用变量存储lambda,就没有可以推断出参数类型的上下文,必须显示的指定参数。
//变量存储lambda,必须显示指定参数类型
var getAge = {person:Person -> person.age}
//输出结果:Person(name=小花, age=20)
println(people.maxBy(getAge))
lambda当然也能包含更多语句。
val sum = { x: Int, y: Int ->
println("this is $x and $y and sum is")
x+y
}
//this is 1 and 2 and sum is
// 3
println(sum(1,2))
在作用域访问变量
lambda表达式有个形影不离的概念:从上下文中捕捉变量。
当在函数内部声明一个匿名内部类的时候,能够在这个匿名内部引用这个函数的参数和局部变量。lambda同样可以。
//使用lambda进行循环的函数
fun p