[Kotlin学习] 5. 匿名函数与函数

第五章 匿名函数与函数

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 定义的函数

posted @ 2022-01-16 17:48  沐灵_hh  阅读(183)  评论(0编辑  收藏  举报