匿名函数
定义时不取名字的函数,称之为匿名函数。
匿名函数通常整体传递给其他函数,或者从其他函数返回。这种交互主要靠函数类型和函数参数实现。
fun main() {
println({
val currentYear = 2020
"Welcome to SimVillage, Mayor! (Copyright $currentYear)"
}())
}
函数类型
匿名函数也有类型,称之为函数类型(function type)。
匿名函数可以当作变量值赋给函数类型变量。然后就像其他变量一样,匿名函数就可以在代码里传递了。
fun main() {
val greetingFunction: () -> String = {
val currentYear = 2020
"Welcome to SimVillage, Mayor! (copyright $currentYear)"
}
println(greetingFunction())
}
大多数情况下匿名函数不需要 return 关键字来返回数据。匿名函数会隐式或自动返回函数体最后一行语句的结果。
之所以不能用 return 关键字,是因为编译器不知道返回数据究竟是来自调用匿名函数的函数,还是匿名函数本身。
Lambda
匿名函数可以改称为 lambda,将它的定义改称为 lambda 表达式,它的返回数据改称为 lambda 结果。
Lambda 就是一小段可以作为参数传递的代码,通常不建议在 Lambda 表达式中编写太长的代码,否则可能会影响代码的可读性。
Lambda 表达式的语法结构:
{参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}
最后一行代码会自动作为 Lambda 表达式的返回值。
简略语法
以找到单词长度最长的水果为例
val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val maxLengthFruit = list.maxBy({ fruit: String -> fruit.length })
println(maxLengthFruit)
并不总是需要使用 Lambda 表达式完整的语法结构,有很多种简化的写法。
maxBy 就是一个普通函数,只不过它接收的是一个 Lambda 类型的参数,并且在遍历集合时将每次遍历的值作为参数传递给 Lambda 表达式。
Kotlin 规定,当 Lambda 参数是函数最后一个参数时,可以将 Lambda 参数移到函数括号的外面。
val maxLengthFruit = list.maxBy() { fruit: String -> fruit.length }
Lambda 参数是函数唯一一个参数的话,可以将函数的括号省略。
val maxLengthFruit = list.maxBy { fruit: String -> fruit.length }
由于 Kotlin 具有出色的类型推导机制,Lambda 表达式中的参数列表绝大多数情况下不必声明参数类型。
val maxLengthFruit = list.maxBy { fruit -> fruit.length }
当 Lambda 表达式的参数列表只有一个参数时,也不必声明参数名,可以使用 it
关键字来代替。
val maxLengthFruit = list.maxBy { it.length }
闭包
Kotlin 中的 lambda 是闭包。
闭包能使用定义自己的外部函数的变量。在 Kotlin 中,匿名函数能修改并引用定义在自己的作用域之外的变量。这表明,匿名函数引用着定义自身的函数里的变量。
fun main() {
runSimulation()
runSimulation()
}
fun runSimulation() {
val greetingFunction = configureGreetingFunction()
println(greetingFunction("Guyal"))
println(greetingFunction("Guyal"))
println(greetingFunction("Guyal"))
}
fun configureGreetingFunction(): (String) -> String {
val structureType = "hospitals"
var numBuildings = 5
return { playerName: String ->
val currentYear = 2020
numBuildings += 1
println("Adding $numBuildings $structureType")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
}
函数式编程
一个函数式应用通常由三大类函数构成:变换(transform)、过滤(filter)和合并(combine)。
每类函数都针对集合数据类型设计,目标是产生一个最终结果。
map
变换函数会遍历集合内容,用一个以值参形式传入的变换器函数变换每一个元素,然后返回包含已修改元素的集合给链上的其他函数。
map 用于将集合中的每个元素都映射成另外的一个值。
val animals = listOf("zebra", "giraffe", "elephant", "rat")
val babies = animals
.map { "A baby $it" }
.map { "$it, with the cutest little tail ever!" }
babies.forEach { println(it) }
flatMap 函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。
// listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatMap { it }
val flatten = listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatten()
println(flatten)
filter
filter 接受一个 predicate 函数,用它按给定条件检查接收者集合里的元素并给出 true 或 false 的判定。
如果 predicate 函数返回 true,受检元素就会添加到过滤函数返回的新集合里。如果 predicate 函数返回 false,那么受检元素就被移出新集合。
val itemsOfManyColors = listOf(
listOf("red apple", "green apple", "blue apple"),
listOf("red fish", "blue fish"),
listOf("yellow banana", "teal banana")
)
val redItems = itemsOfManyColors.flatMap { list -> list.filter { it.contains("red") } }
print(redItems)
val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 11)
val primes = numbers.filter { number ->
(2 until number).map { number % it }.none { it == 0 }
}
print(primes)
any、all
any 函数用于判断集合中是否至少存在一个元素满足指定条件。
all 函数用于判断集合中是否所有元素都满足指定条件。
val anyResult = list.any { it.length <= 5 }
val allResult = list.all { it.length <= 5 }
combine
合并函数能将不同的集合合并成一个新集合(这和接收者是包含集合的集合的 flatMap 函数不同)。
val list1 = listOf("A", "B", "C")
val list2 = listOf("1", "2", "3")
val map = list1.zip(list2).toMap()
println(map)
println(map["A"])
val foldedValue = listOf(1, 2, 3, 4).fold(0) { accumulator, number ->
println("Accumulated value: $accumulator")
accumulator + (number * 3)
}
println("Final value: $foldedValue")
调用 Java 方法时的函数式 API 使用
Kotlin 中调用 Java 方法,并且该方法接收一个 Java 单抽象方法接口参数,就可以使用函数式 API。
Java 单抽象方法接口指的是接口中只有一个待实现方法,如果有多个待实现方法,则为无法使用。
以 Kotlin 中 start 一个线程为例:
Thread(object : Runnable {
override fun run() {
println("Thread is running.")
}
}).start()
目前只是简单把 Java 中的写法使用 Kotlin 写了一遍,Kotlin 完全舍弃了 new 关键字,因此创建匿名类实例的时候就不能再用 new 了,而是改用了 object 。
Thread 类的构造方法就符合 Java 函数式 API 的使用条件
Thread(Runnable {
println("Thread is running.")
}).start()
因为 Runnable 中只有一个待实现的方法,即使没有显式的重写 run()
方法,Kotlin 也能明白 Runnable 后面的 Lambda 表达式就是要在 run()
方法中实现的内容。
和 Kotlin 中函数式API 使用类似,当 Lambda 表达式是方法的最后一个参数时,可以将 Lambda 表达式移到方法的括号外面,同时 Lambda 表达式还是方法的唯一一个参数,还可以将方法的括号省略。
Thread { println("Thread is running.") }.start()
不过在 Java 8 后同样的逻辑 Java 代码也可以很简略,比较熟悉的应该都很清楚了。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running.")
}
}).start();
new Thread(() -> System.out.println("Thread is running.")).start();
在满足方法接收的是 Java 单抽象方法接口参数时,于方法而言,知道接收的是什么,所以不用写具体的接口名。
对接口而言,因为接口中只有一个待实现方法,所以不需要写出方法名,不需要显式的重写,直接在大括号中写出接口中方法的逻辑即可。
因为 Android SDK 还是用 Java 语言编写的,所以在 Kotlin 中调用这些 SDK 接口时,就可能会用到这种 Java 函数式 API 的写法。