名词解释
函数字面值:指不声明而是直接作为表达式传递的函数,lambda表达式和匿名函数都是函数字面值
一、Lambda表达式
1.1、语法形式如下:
Kotlin
val doubleMax: (Int, Int) -> Int = { x, y ->
val sum = x.coerceAtLeast(y)
sum + sum
}
//或者 和属性一样可以省略函数类型,自行推断
val doubleMax = { x: Int, y: Int ->
val sum = x.coerceAtLeast(y)
sum + sum
}
- 表达式总是括在花括号内
- 函数体跟在
->
符号后面 - 如果推断出该lambda的返回类型不是
Unit
,那么该lambda主体中的最后一个表达式的值会视为返回值
1.2、传递末尾的lambda表达式
按照Kotlin的惯例,如果函数的最后一个参数是函数,那么作为相应参数传入的lambda表达式可以放在圆括号之外:
Kotlin
//高阶函数--可以将函数作为参数传递的函数
fun cal(x: Int, comparator: (Int, Int) -> Int): Int {
return x * comparator(x, 2)
}
//调用
cal(100) { x, y -> x * y }
//结果:100*100*2 = 20000
如果该lambda表达式是函数的唯一参数,那么圆括号可以省略
Kotlin
fun cal(comparator: (Int, Int) -> Int): Int {
return 100 * comparator(100, 3)
}
//调用
cal{ x, y -> x * y }
//结果:100*100*3 = 30000
1.3、it:单个参数的隐式名称
如果一个lambda表达式只有一个参数,则定义表达式时可以省略参数名,该参数会隐式声明为it
Kotlin
fun cal(comparator: (Int) -> Int): Int {
return comparator(100)
}
//调用
cal { it * it }
//等价于(it可省略)
cal { it -> it * it }
1.4、lambda表达式返回值
可以使用限定返回语法从lambda显式返回一个值。否则,将隐式返回最后一个表达式的值
Kotlin
val list = mutableListOf<Int>()
list.add(100)
list.add(-100)
val list2 = list.filter {
return@filter it > 0
}
//等价于
val list2 = list.filter {
it > 0
}
1.5 下划线用于未使用的变量
如果lambda表达式的参数未使用,可以用下划线取代其名称
Kotlin
map.forEach { (_, value) -> println("$value!") }
1.6、表达式返回
在Kotlin中,只能对具名或者匿名函数使用正常的、非限定的return
来退出。要退出一个lambda表达式,需要一个标签。
在lambda表达式内部禁止使用裸return
,因为lambda表达式不能使包含它的函数返回
Kotlin
val doubleMax = test@{ x: Int, y: Int ->
val sum = x.coerceAtLeast(y)
return@test sum + sum
}
val doubleMax2 = { x: Int, y: Int ->
val sum = x.coerceAtLeast(y)
return sum + sum //错误
}
list.filter {
return it > 0 //错误
}
但是如果lambda表达式是传给的函数是内联的,则return也可以内联。如下kotlin.collections
的forEach
函数是内联函数,可以这样:
Kotlin
list.forEach {
sum += it
if (sum > 100) {
return
}
}
二、匿名函数
上文的lambda表达式语法缺少一个能力------指定函数的返回类型的能力。大都是情况下是不需要的,返回类型可以自动推断出来。当然,如果确实需要显示指定,可以使用另一种语法:匿名函数
Kotlin
fun(x: Int, y: Int):Int = x+y
//或者
fun(x: Int, y: Int): Int {
return x + y //return不可省略
}
匿名函数看起来非常像一个常规函数声明,除了其名称省略了。
匿名函数的返回类型推断机制和正常函数一样:单表达式函数体的匿名函数将自动推断返回类型,但是具有代码块函数体的返回类型必须显式指定(Unit
返回类型不需要指定)
三、Function接口
Kotlin decompile转换成Java文件后,可以看出lambda表达式和匿名函数本质上对应的是Function
接口。lambda表达式和匿名函数会被转换成一个个Function
接口对象
Kotlin
package kotlin.jvm.functions
/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
/** Invokes the function. */
public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}
...
public interface Function22<...> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(...): R
}
最多支持22个参数,当然我们也可以自己扩展
四、闭包
闭包概念:参考文章
函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包。
周围环境应该如何理解呢,可以理解为函数所处的外部作用域中定义的各个自由变量,这个作用域可能是另一个函数或块级作用域。二者捆绑在一起构成的闭包使得函数内部可以对外部作用域定义的变量进行访问。
Lambda表达式或者匿名函数(以及局部函数)可以访问闭包,即在外部作用域中声明的变量。 在 lambda 表达式中可以修改闭包中捕获的变量。示例:
Kotlin
fun main() {
val list = mutableListOf<Int>()
list.add(2)
list.add(3)
list.add(-1)
var sum = 0
list.filter { it > 0 }.forEach {
//这里可以访问sum属性
sum += it
}
}