函数类型与Lambda表达式
在Kotlin高阶函数中,涉及到了两个比较重要的概念,那就是Kotlin的函数类型与Lambda表达式。
其实,Lambda表达式是函数类型的一个实例。
怎么说呢,Kotlin中把类似的声明(T) -> R称作函数类型。具体点的比如(Int) -> String,就是说这里的参数是一个函数类型,函数的入参为Int类型,返回值是String类型。
另外,如果函数类型有多个入参,比如(A, B) -> C。
如果没有参数的函数类型,则表示为() -> R。
需要注意的是,函数类型中,如果返回值为Unit的,不能和函数一样省略掉。
fun func(a: Int, p: (p: String) -> Unit) {
}
这样是OK的,但是如果Unit不写,编译器就会提示缺少类型声明。
另外中函数类型的表示方法为A.(B) -> C,表示A调用的是一个(B)-> C的返回值,这种情况下,很多时候用于C是一个函数类型。也称作“带有接收者的函数字面值”,听起来很玄乎的,其实和扩展函数类似。
val sum: Int.(Int) -> Int = { other -> plus(other) }
是不是看不太懂,用扩展函数重写一下
fun Int.p(p: Int) {
plus(p)
}
或者写成这样
val sum = fun Int.(other: Int): Int = this + other
再来重新一下第一个
val sum: Int.(Int) -> Int = {
other -> this.plus(other)
}
this就代表的是Int,最终就是A类型调用它的扩展函数。看一个比较高大上的
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
被称之为Kotlin语法糖之一的let函数。关于Kotlin语法糖,我们下一篇单独来说,看完之后,你就会对Kotlin深爱。
另外有一个比较特殊的函数类型,使用suspend来修饰的,称为挂起函数。挂起函数用在协程当中,协程是Kotlin异步的最佳实践。我们后面会抽专门的篇幅来介绍协程。
关于函数类型,还有几个点:
- 如需将函数类型指定为可空,请使用圆括号:
((Int, Int) -> Int)?
- 函数类型可以使用圆括号进行接合:
(Int) -> ((Int) -> Unit)
- 箭头表示法是右结合的,
(Int) -> (Int) -> Unit
与前述示例等价,但不等于((Int) -> (Int)) -> Unit
。 - 可以通过使用类型别名给函数类型起一个别称。
typealias ClickHandler = (Button, ClickEvent) -> Unit
有别称的函数类型,后续调用可以直接使用别称。
关于函数类型说完了,总结一下,如果一个函数需要传入函数类型,有哪些办法可以得到函数类型的实例传入呢?
- Lambda表达式和匿名函数。
- 使用已有声明的可调用引用。
- 顶层、局部、成员、扩展函数:
::isOdd
、String::toInt
, - 顶层、成员、扩展属性:
List<Int>::size
, - 构造函数:
::Regex
- 使用实现函数类型接口的自定义类的实例,如
class IntTransformer: (Int) -> Int {
override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()
需要注意的是,在获取函数类型实例时,如果有足够的信息使得编译器能够推断出类型的话,其类型可以省略。
函数类型的值可以通过其 invoke(……)
操作符调用:f.invoke(x)
或者直接 f(x)
。
上面已经提及好几次Lambda表达式了,接下来,就看看Lambda表达式是如何定义的。
先引用官方原话:lambda 表达式与匿名函数是“函数字面值”,即未声明的函数, 但立即做为表达式传递。
然后具体举例说明。
说是“函数字面值”,也就是说Lambda表达式就相当于一个变量,用没有名字的函数表示了,相当于
{a, b -> a + b}
简单来说,就一段代码块。
对于匿名函数来说,就比lambda表达式多了一个fun关键字。
val sum: (Int, Int) -> Int = {a, b ->
a + b
}
val sum2 = fun (a: Int, b: Int): Int = a + b
另外,当lambda表达式的参数唯一时,可以省略圆括弧。
如可以使用的标准库函数
run {print("Hello")}
另外,lambda表达式参数唯一时,如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->
。 该参数会隐式声明为 it
如
val sum: (Int) -> Int = {
1
}
如果使用编译器,则可以看到编译器对应的提示
Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其闭包 ,即在外部作用域中声明的变量。
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
是不是觉得Kotlin的魅力越来越大啦
关于函数类型与Lambda表达式,就说到这儿,下一篇,学习Kotlin的语法糖,让你彻底爱上它。