第五章 匿名函数与函数
5.1 匿名函数
fun main(args: Array<String>) {
println({
val currentYear = 2022
"Welcome to SimVillage, Mayor! (copyright $currentYear)"
}())
}
定义函数就是把表达式或语句放在一对花括号里
在花括号后面跟上一对空的圆括号,表示调用匿名函数
5.1.1 函数类型
匿名函数也有类型,叫做函数类型(function type)
匿名函数可以当作变量赋值给函数类型变量
把匿名函数赋值给变量:
fun main(args: Array<String>) {
val greetingFunction: () -> String = {
val currentYear = 2022
"Welcome to SimVillage, Mayor! (copyright $currentYear)"
}
println(greetingFunction())
}
: () -> String
表示变量存储的是一个函数,这个函数不需要参数,且返回值是 String
5.1.2 隐式返回
隐式返回:匿名函数会隐式或自动返回函数体最后一行语句的结果
和具名函数不一样,除了极少数的情况外,匿名函数不需要 return
关键字来返回数据
匿名函数不用 return
关键字的原因:编译器不知道返回数据究竟是来自调用匿名函数的函数,还是函数本身
5.1.3 函数参数
匿名参数也可以带一个或多个任何类型的参数
fun main(args: Array<String>) {
val greetingFunction: (String) -> String = { playerName ->
val currentYear = 2022
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
println(greetingFunction("Guyal"))
}
在匿名函数体内,左花括号后面,写上 String
类型的参数名,后面再跟上一个箭头符号
5.1.4 it
关键字
定义只有一个参数的匿名函数时,可以使用 it
关键字来表示参数名
fun main(args: Array<String>) {
val greetingFunction: (String) -> String = {
val currentYear = 2022
"Welcome to SimVillage, $it! (copyright $currentYear)"
}
println(greetingFunction("Guyal"))
}
5.1.5 多个参数
it
关键字只适用于一个参数的情况,如果有多个参数,需要使用命名参数
fun main(args: Array<String>) {
val greetingFunction: (String, Int) -> String = { playerName, numBuildings ->
val currentYear = 2022
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
println(greetingFunction("Guyal", 2))
}
类型推断
定义一个变量时,如果已把匿名函数作为变量赋值给它,就不需要显示指明变量类型
val greetingFunction = {
val currentYear = 2022
"Welcome to SimVillage, Mayor! (copyright $currentYear)"
}
类型推断也支持带参数的匿名函数,但是匿名函数必须有参数名和参数类型
fun main(args: Array<String>) {
val greetingFunction = { playerName: String, numBuildings: Int ->
val currentYear = 2022
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
println(greetingFunction("Guyal", 2))
}
5.3 定义参数是函数的函数
术语:
- lambda: 匿名函数
- lambda 表达式: 匿名函数的定义
- lambda 结果: 匿名函返回的数据
函数也可作为函数参数
fun main(args: Array<String>) {
val greetingFunction = { playerName: String, numBuildings: Int ->
val currentYear = 2022
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
runSimulation("Guyal", greetingFunction)
}
fun runSimulation(playerName: String, greetingFunction: (String, Int) -> String) {
val numBuildings = (1..3).shuffled().last() // Randomly selects 1, 2, or 3
println(greetingFunction(playerName, numBuildings))
}
简略语法
如果一个函数的 lambda 参数排在最后,那么括住 lambda 值参的一堆圆括号就可以省略
"Mississippi".count({ it == 's' })
就可以简写成
"Mississippi".count { it == 's' }
同样 runSimulation
也可使用简略语法传入 lambda 值参:把非函数类型的值放在圆括号内,把函数值参放在括号外
fun main(args: Array<String>) {
runSimulation("Guyal") { playerName, numBuildings ->
val currentYear = 2022
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
}
fun runSimulation(playerName: String, greetingFunction: (String, Int) -> String) {
val numBuildings = (1..3).shuffled().last() // Randomly selects 1, 2, or 3
println(greetingFunction(playerName, numBuildings))
}
5.4 函数内联
在 JVM 上,lambda 会以对象实例的形式存在。JVM 会为所有同 lambda 打交道的变量分配内存,这样就产生了内存开销
Kotlin 中可以通过内联解决 lambda 引起的内存开销问题,避免变量内存分配
使用内联方法优化 lambda: 以 inline
关键字标记使用 lambda 的函数即可
inline fun runSimulation(playerName: String, greetingFunction: (String, Int) -> String) {
val numBuildings = (1..3).shuffled().last() // Randomly selects 1, 2, or 3
println(greetingFunction(playerName, numBuildings))
}
有了 inline
关键字后,调用 runSimulation
函数就不会使用 lambda 对象实例了:哪里需要使用 lambda,编译器就会将函数体复制粘贴到哪里
使用 lambda 的递归函数无法内联,因为内联函数会让复制粘贴函数体的行为无限循环
5.5 函数引用
除了传 lambda 表达式,Kotlin 还提供了“传递函数引用”方法来讲函数作为参数传给其他函数使用
使用 ::
+ 函数名称操作符获得函数引用
5.6 函数类型作为返回值
闭包:使用定义自己的外部函数的变量的函数
Kotlin 中的 lambda 是闭包
fun configureGreetingFunction(): (String) -> String {
val structureType = "hospitals"
var numBuildings = 5
return { playerName: String ->
val currentYear = 2022
numBuildings += 1
println("Adding $numBuildings $structureType")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
}
5.7 深入学习:Kotlin 中的 lambda 就是闭包
在 Kotlin 中,匿名函数能修改并引用定义在自己的作用域之外的变量。这表明,匿名函数引用着定义自身的函数里的变量
在 runSimulation
中调用 println
两次
fun runSimulation() {
val greetingFunction = configureGreetingFunction()
println(greetingFunction("Guyal"))
println(greetingFunction("Guyal"))
}
输出为:
Adding 6 hospitals
Welcome to SimVillage, Guyal! (copyright 2022)
Adding 7 hospitals
Welcome to SimVillage, Guyal! (copyright 2022)
numBuildings
变量的值从 6 增加到了 7
5.8 深入学习:lambda 与匿名内部类
使用函数类型的好处:函数类型能让开发者少写模式化代码,写出更为灵活的代码
Java 8 支持面向对象编程和 lambda 表达式,但不支持讲函数作为参数传给另一个函数或变量。不过 Java 的替代方式是匿名内部类——定义在类中,用来实现某个方法的无名类
Greeting greeting = (palyerName, numBuildings) -> {
int currentYear = 2022;
System.out.println("Adding " + numBuildings + " houses");
return "Welcom to SimVillage, " + playerName + "! (copyright " + currentYear + ")";
};
public interface Greeting {
String greet(String playerName, int numBuildings);
}
greeting.greet("Guyal", 6);
这种方法和 Kotlin 传递 lambda 表达式差不多,但是 Java 需要一个命名接口或类来代表 lambda 定义的函数