一、Lambda表达式语法
回顾上一篇,我们在某个地方需要运行一个函数,我们只知道这个函数的参数类型和返回值类型(参数类型加返回值类型构成了 kotlin一种特殊的数据类型:函数类型),但函数具体执行什么操作我们并不关心,所以我们动态的传递一个满足参数类型和返回值类型条件的函数,但只有对象才能作为参数传递,所以我们通过双冒号或匿名函数 来实现一个函数类型的对象,但这两种方式代码比较繁琐,为了简化代码量,使代码简洁明了,我们引入了Lambda表达式,所以Lambda表达式的本质其实是函数类型的对象,我们真正的诉求就是想要一个动态设置的方法。
示例:现在有一个方法a,该方法的参数类型是一个函数类型
fun a(fb : (Int , String) -> String) {
}
为了能调用方法a,我们需要传入一个函数类型的对象,
我们一般有两种方法:
- 可以使用双冒号:
fun b(p1 : Int , p2 : String) : String {
return p2 + p1
}
a(::b)
- 使用匿名函数
val b : (Int , String) -> String = fun(p1 : Int , p2 : String) : String {
return p2 + p1
}
a(b)
当然也可以直接把匿名函数传进去:
a(fun(p1 : Int , p2 : String) : String {
return p2 + p1
})
可以看到匿名函数的方式过于繁琐了,接下来我们引入Lambda表达式进行简化
1. )Lambda表达式格式如下:
{参数1:参数类型,参数2:参数类型,… -> 执行代码块 }
注意:执行代码块里最后一行表达式就是Lambda表达式的返回值
,该表达式的返回类型必须与声明的返回值类型一致。
所以上面的匿名函数我们可以用Lambda表达式表示为:
a({ p1 : Int , p2 : String ->
p2 + p1
})
2.)当我们已经显示声明lambda的函数类型时,可以省去lambda参数列表中参数的类型
所以可以简化为:
a({ p1 , p2 ->
p2 + p1
})
3.)当lambda表达式作为函数调用的最后一个实参,可以将它放在括号外边
所以可以简化为:
a() { p1 , p2 ->
p2 + p1
}
4.)当lambda是函数唯一的实参时,还可以将函数的空括号去掉
所以可以简化为:
a { p1 , p2 ->
p2 + p1
}
经过不断的简化,这就是我们日常最常见的形式了。
5.)当lambda表达式中只有一个参数,并且这个参数的类型能推导出来时,会生成默认参数名称it来代替这个参数,此时我们不需要显示的声明该参数的名称,也就是说可以省略掉
示例如下:
fun a(fb : (Int ) -> String) {
}
a{
it.toString()
}
注意:当多个lambda嵌套的情况下,最好显式地声明每个lambda表达式的参数,否则很难搞清楚it引用的到底是什么值,严重影响代码可读性。
6.)在使用Lambda表达式的时候,可以用下划线(_)表示执行代码块中未使用的参数,表示不处理这个参数。
a { _ , p2 ->
p2
}
二、SAM 转换
我们在上一篇中,也提到过Java传递函数的方法,使用接口,kotlin也提供了对接口的lambda语法,也就是SAM 转换:
-
SAM转换只适用于接口,不适用于抽象类,即使这些抽象类也只有一个抽象方法。
-
必须是函数式接口,也就是使用fun interface 声明的接口,该接口只能有一个抽象方法(很显然,lambda表达式的最终目的就是想要一个动态的函数)
SAM 转换就是将lambda显示转换为函数式接口实例而生成的函数 -
函数式接口和函数类型不是相同的东西,这很好理解,一个是接口一个是数据类型,不能混用
示例如下:我们定义一个函数式接口
fun interface Click {
fun click(a : Int) : String
}
fun a(c : Click) {
c.click(1)
}
在不使用lambda情况下,为了方便,我们一般使用对象表达式构建一个匿名内部类的对象传入
a(object : Click {
override fun click(a : Int) : String {
return a.toString()
}
})
使用lambda,并且语法和上面提到的没有任何区别
a { a -> a.toString() }