阅读完需:约 12 分钟
Kotlin 函数都是头等的,这意味着它们可以存储在变量与数据结构中、作为参数传递给其他高阶函数以及从其他高阶函数返回。可以像操作任何其他非函数值一样操作函数。
为促成这点,作为一门静态类型编程语言的 Kotlin 使用一系列函数类型来表示函数并提供一组特定的语言结构,例如 lambda 表达式。
匿名函数


返回类型可以自动推断出来。然而,如果确实需要显式指定,可以使用另一种语法: 匿名函数 。
fun(x: Int, y: Int): Int = x + y
val func: () -> Unit = fun() {
println("Hello")
}
匿名函数可以赋值给其他变量。
匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体可以是表达式(如上所示)或代码块:
fun(x: Int, y: Int): Int {
return x + y
}
匿名函数的返回类型推断机制与正常函数一样:对于具有表达式函数体的匿名函数将自动推断返回类型,而具有代码块函数体的返回类型必须显式指定(或者已假定为 Unit
)。
请注意,匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。
Lambda表达式和匿名函数之间的另一个区别是非局部返回的行为。
一个不带标签的 return{: .keyword } 语句总是在用 fun{: .keyword } 关键字声明的函数中返回。这意味着 lambda 表达式中的 return{: .keyword }将从包含它的函数返回,而匿名函数中的 return{: .keyword }将从匿名函数自身返回。
闭包
Lambda 表达式或者匿名函数(以及局部函数和对象表达式)可以访问其 闭包 ,即在外部作用域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的变量:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
Lambda 表达式 就是从匿名函数中进一步变化而来的。
Lambda 表达式
Lambda
是一个没有名字的函数。 Lambda
是用花括号{}
定义的,它将变量作为参数(如果有的话)和函数体。 函数体在变量(如果有)之后写入,后跟 ->
运算符。

表达式中没有处理的东西仅仅是为了打印。
val sum = { x: Int, y: Int -> x + y }
如果推断出的该 lambda 的返回类型不是 Unit
,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。
如果我们把所有可选标注都留下,看起来如下:
val sum: (Int, Int) -> Int = { x, y -> x + y }
it
:单个参数的隐式名称
一个 lambda 表达式只有一个参数是很常见的。

如果编译器自己可以识别出签名,也可以不用声明唯一的参数并忽略 ->
。 该参数会隐式声明为 it
:
ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的
从 lambda 表达式中返回一个值

我们可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。
因此,以下两个片段是等价的:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
如果一个函数接受另一个函数作为最后一个参数,lambda 表达式参数可以在圆括号参数列表之外传递。
lambda中的返回语句:从一个封闭的函数返回
比较两种不同的遍历集合的方法
data class Person(
val name: String,
val age: Int
)
val persons = listOf(Person("Alice", 23), Person("Bob", 43))
lookForAlice(persons)
fun lookForAlice(persons: List<Person>) {
for (person in persons) {
if (person.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not Found!")
}
// Found!
如果使用forEach迭代重写这段代码安全吗?使用forEach也是安全的。
fun lookForAlice(persons: List<Person>) {
persons.forEach {
if (it.name == "Alice") {
println("Found!")
return
}
}
println("Alice is not Found!")
}
// Found!
如果在lambda中使用return关键字,它会从调用lambda的函数中返回,并不只是从lambda中返回。这样的return语句叫作非局部返回,因为它从一个比包含return的代码块更大的代码块中返回了。
只有在以lambda作为参数的函数是内联函数的时候才能从更外层的函数返回。forEach的函数体和lambda的函数体一起被内联了,所以在编译的时候很容易做到从包含它的函数中返回。在一个非内联函数的lambda中使用return表达式是不允许的。
从lambda返回:使用标签返回
也可以在lambda表达式中使用局部返回。lambda中的局部返回跟for循环中的break表达式相似。它会终止lambda的执行,并接着从调用lambda的代码处执行。
要区分局部返回还是非局部返回,要用到标签。如果想从一个lambda表达式处返回,可以标记它,然后在return关键字后面引用这个标签。
// 用一个标签实现局部返回
fun lookForAlice(persons: List<Person>) {
// 在lambda表达式上加标签
persons.forEach label@{
if (it.name == "Alice") {
// return@label引用了这个标签
return@label
}
}
println("Alice is not Found!")
}
要标记一个lambda表达式,在lambda的花括号之前放一个标签名(可以是任何标识符),接着跟一个@符号。要从一个lambda返回,在return关键字后跟一个@符号,接着跟标签名。
使用lambda作为参数的函数的函数名可以作为标签:
// 用函数名作为return标签
fun lookForAlice(persons: List<Person>) {
persons.forEach {
if (it.name == "Alice") {
// return@forEach从lambda表达式返回
return@forEach
}
}
println("Alice is not Found!")
}
匿名函数:默认使用局部返回
// 在匿名函数中使用return
fun lookForAlice(persons: List<Person>) {
// 使用匿名函数取代lambda表达式
persons.forEach(fun(person) {
// return指向最近的函数:一个匿名函数
if (person.name == "Alice") return
println("${person.name} is not Alice!")
})
}
// Bob is not Alice!
匿名函数省略了函数名和参数类型。
// 在filter中使用匿名函数
persons.filter(fun(person): Boolean {
return person.age < 30
})
如果使用表达式函数体,就可以省略返回值类型
// 使用表达式函数体匿名函数
persons.filter(fun(person) = person.age < 30)
在匿名函数中,不带标签的return表达式会从匿名函数返回,而不是从包含匿名函数的函数返回。return从最近的使用fun关键字声明的函数返回。
lambda表达式没有使用fun关键字,所以lambda中的return从最外层的函数返回。匿名函数使用了fun关键字,是从最近fun声明的函数返回。
带接收者的函数字面值
Kotlin 提供了使用指定的 接收者对象 调用函数字面值的功能。在函数字面值的函数体中,可以调用该接收者对象上的方法而无需任何额外的限定符。这类似于扩展函数,它允你在函数体内访问接收者对象的成员。其用法的最重要的示例之一是类型安全的 Groovy-风格构建器。
这样的函数字面值的类型是一个带有接收者的函数类型:
sum : Int.(other: Int) -> Int
该函数字面值可以这样调用,就像它是接收者对象上的一个方法一样:
1.sum(2)
匿名函数语法允许你直接指定函数字面值的接收者类型如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用。
val sum = fun Int.(other: Int): Int = this + other
当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。
class HTML {
fun body() { …… }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 创建接收者对象
html.init() // 将该接收者对象传给该 lambda
return html
}
html { // 带接收者的 lambda 由此开始
body() // 调用该接收者对象的一个方法
}
Lambda 表达式推导

原本的表达式没有传值,并且没有返回类型所以是 ()-> Unit




Lambda 表达式例子:
一般函数:两个数字相加
在这个例子中,创建一个函数addNumber()
,它传递从main
函数调用的两个参数(a
,b
)。
fun main(args: Array<String>){
addNumber(5,10)
}
fun addNumber(a: Int, b: Int){
val add = a + b
println(add)
}
执行上面示例代码,得到以下结果
15
Lambda函数:两个数字相加
上面的程序使用lambda
函数重写如下:
fun main(args: Array<String>){
val myLambda: (Int) -> Unit= {s: Int -> println(s) } //lambda function
addNumber(5,10,myLambda)
}
fun addNumber(a: Int, b: Int, mylambda: (Int) -> Unit ){ //high level function lambda as parameter
val add = a + b
mylambda(add) // println(add)
}
返回:
15
在上面的程序中,创建一个lambda
表达式{s: Int -> println(s) }
,其返回类型为Unit
。 lambda
函数作为高级函数addNumber(5,10,myLambda)
中的参数填充。 函数定义中的变量mylambda
实际上是一个lambda
函数。 mylambda
的函数主体已经在lambda
函数中给出。
Lambda 表达式引发的8种写法
当 Lambda 表达式作为函数参数的时候,有些情形下是可以简写的,这时候可以让我们的代码看起来更简洁。
要理解 Lambda 表达式的简写逻辑,其实很简单,那就是:多写
。
各位小伙伴可以跟着我接下来的流程来一起写一写:
第1种写法
这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类
:
image.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
gotoPreview(v)
}
})
第2种写法
如果我们删掉 object
关键字,它就是 Lambda 表达式了,因此它里面 override 的方法也要跟着删掉:
image.setOnClickListener(View.OnClickListener { view: View? ->
gotoPreview(view)
})
上面的 View.OnClickListener
被称为:SAM Constructor
—— SAM 构造器,它是编译器为我们生成的。Kotlin 允许我们通过这种方式来定义 Lambda 表达式。
第3种写法
由于 Kotlin 的 Lambda 表达式是不需要 SAM Constructor
的,所以它也可以被删掉。
image.setOnClickListener({ view: View? ->
gotoPreview(view)
})
第4种写法
由于 Kotlin 支持类型推导
,所以 View?
可以被删掉:
image.setOnClickListener({ view ->
gotoPreview(view)
})
第5种写法
当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it
。
image.setOnClickListener({ it ->
gotoPreview(it)
})
第6种写法
Kotlin Lambda 的 it
是可以被省略的:
image.setOnClickListener({
gotoPreview(it)
})
第7种写法
当 Kotlin Lambda 作为函数的最后一个参数时,Lambda 可以被挪到外面:
image.setOnClickListener() {
gotoPreview(it)
}
第8种写法
当 Kotlin 只有一个 Lambda 作为函数参数时,()
可以被省略:
image.setOnClickListener {
gotoPreview(it)
}