Lambda表达式(简称 lambda):本质上是可以传递给其他函数的一小段代码
一、lambda表达式的语法
1、Kotlin 的 lambda 表达式始终用花括号包围
{x:Int, y:Int -> x+y} //lambda表达式的语法, x和y是参数,x+y是函数体,->箭头把实参列表和lambda的函数体隔开
2、可将 lambda 表达式存储在一个变量中,把这个变量当作普通函数对待
val sum = {x:Int, y:Int -> x+y}
println(sum(1,2))
3、lambda 的简化写法
代码:
people.maxBy{it.age}
其全面的写法如下:
people.maxBy({ p:Person -> p.age }) //p是参数,p.age是函数体
接着将最全面的写法一步步简化:
1)Kotlin语法约定:如果lambda表达式是函数调用的最后一个实参,它可以放到括号的外边。
people.maxBy(){p:Person->p.age}
2)Kotlin语法约定:当lambda时函数唯一的实参时,可以去掉调用代码中的空括号对
people.maxBy{p:Person->p.age}
3)maxBy函数的参数类型始终和集合的元素类型相同,所以编译器可以根据people(Person对象的集合)推断出 maxBy 参数类型是 Person。所以,参数类型可省略
people.maxBy{p->p.age}
4)使用默认参数名称 it 来代替命名参数 p
people.maxBy{it.age}
使用 it 的场景:如果当前上下文期望的是只有一个参数的 lambda 且这个参数的类型可以推断出来时使用。
如果用变量存储 lambda,那么就没有可以推断出参数类型的上下文,这时必须显式地指定参数类型:
val getAge = { p:Person -> p.age }
people.maxBy(getAge)
4、成员引用
成员引用的格式:类名称 + 双冒号 + 要引用的成员(方法或属性)名称
注意,即使引用方法也不能在方法名后面加括号
引入成员引用的目的:lambda 可以将代码块作为参数传递给函数,但是如果这个代码块已经被定义成了函数,这时可使用成员引用,将函数转换成一个值。
1)
val getAge = Person::age //这种表达式称为 成员引用
等同于 lambda 表达式:
val getAge = {person:Person->person.age}
2)
people.maxBy(Person::age)
等同于 lambda 表达式:
people.maxBy{person:Person->person.age}
3) 还可以引用顶层函数(不是类的成员)
fun sun() = println("sun")
fun main(args:Array<String>){
run(::sun) //引用顶层函数
}
4)将 lambda 委托给一个接收多个参数的函数
fun sendEmail(name:String,message:String){
println("发送给${name}的信息为$message")
}
fun main() {
val action = {
name:String,message:String ->
sendEmail(name, message)
}
val actionNext = ::sendEmail
action("Java","你好啊!")
actionNext("Kotlin","你好啊!")
}
这里不是很明白,明明可以直接调用函数,为什么还要借助lambda呢(之后再说)
5)可以用 构造方法引用 存储 或 延期执行 创建类实例的动作
data class Person(val name:String, val age:Int)
fun main(args:Array<String>){
val createPerson = ::Person
val p = createPerson("Alice", 29)
println(p)
}
6)还可引用扩展函数
data class Person(val name:String, val age:Int)
fun Person.isAdult():Boolean = age>=18
fun main(args:Array<String>){
val createPerson = ::Person
val p = createPerson("Alice", 29)
val predicate = Person::isAdult //引用扩展函数
println(predicate(p))
println(p.isAdult())
}
二、lambda 主要用途
1、lambda 被当作只有一个方法的匿名对象的替代品
//Java 使用匿名对象来实现事件监听器
button.setOnClickListener(new OnClickListener(){ //相当于实现了一个接口的匿名类
@Override
public void onClick(View v){
//点击后执行的动作
... ...
}
})
在上述代码中 button.setOnClickListener 的参数是通过匿名内部类得到的,传递该参数的目的是调用其实现的 onClick 方法,可以理解为,由于 Java 不允许传递方法,所以将 onClick 方法包进 OnClickListener 类型的对象中来进行传递。
而在 Kotlin 中,函数的参数也可以是函数类型的。
//Kotlin 使用lambda实现事件监听器
button.setOnClickListener{
//点击后执行的动作
... ...
}
这种方式可以工作的原因是 OnClickListener 接口只有一个抽象方法,即OnClickListener 接口为函数式接口。Kotlin 允许在调用接收 函数式接口 作为参数的方法时使用 lambda。
函数式接口(SAM接口,SAM表示单抽像方法):只有一个抽象方法的接口
2、lambda和集合一起工作
Kotlin 标准库中和函数有关的API:
1)filter
filter函数遍历集合并选出应用给定lambda后会返回true的那些元素,得到一个新集合,只包含输入集合中满足判断式的元素(注意,filter并不会改变这些元素)
val list = listOf(1,2,3,4)
println(list.filter{ it % 2 == 0 }) //得到一个新的集合,只包含偶数
2)map
对集合中的每一个元素应用给定的函数并把结果收集到一个新集合(注意,map会涉及到元素的变化)
val list = listOf(1,2,3,4)
println(list.map{it*it}) //1,4,9,16
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map{it.name}) //[Alice, Bob]
//或者用成员引用
println(people.map(Person::name)) //[Alice, Bob]
people.filter{ it.age>30 }.map(Person::name) //打印年龄超过30岁的人的名字
3)all
判断是否集合中的所有元素都满足判断式
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClue27 = { p:Person -> p.age<=27 }
println(people.all(canBeInClue27))
4)any
检查集合中是否至少存在一个匹配的元素
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val canBeInClue27 = { p:Person -> p.age<=27 }
println(people.any(canBeInClue27))
5)count
集合中有多少个元素满足判断式
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.count(canBeInClue27))
6)find
找到集合中的一个满足判断式的元素,如果有多个匹配的元素就返回其中第一个元素
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.find(canBeInClue27))
7)groupBy
把列表转换成分组的map
val people = listOf(Person("Alice", 31), Person("Bob", 29), Person("Carol", 31))
println(people.groupBy{it.age})
得到的结果是一个Map<Int, List< Person >>
{29=[Person(name=Bob, age=29)], 31=[Person(name=Alice, age=31), Person(name=Carol, age=31)]}
8)flatMap 和 flatten
flatMap 做了两件事:① 首先根据作为实参给定的函数(即lambda表达式)对集合中的每个元素做变换(或者说映射);② 然后把多个列表合并(或者说平铺)成一个列表
class Book(val title:String, val authors:List<String>)
books.flatMap{it.authors}.toSet()
val strings = listOf("abc", "def")
println(strings.flatMap{it.toList()}) //{a,b,c,d,e,f}
Kotlin标准库中关于集合操作的函数还有很多,这里只介绍这些
参考:
《Kotlin实战》