Osmanthus

空想具現化


  • 首页
  • 归档
  • 分类
  • 标签
  • 关于
  •   

© 2024 Homurax

UV: | PV:

Theme Typography by Makito

Proudly published with Hexo

Kotlin 基础04 - Lambda、函数式编程

发布于 2020-04-06 Kotlin  Kotlin 基础 

匿名函数

定义时不取名字的函数,称之为匿名函数。

匿名函数通常整体传递给其他函数,或者从其他函数返回。这种交互主要靠函数类型和函数参数实现。

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 的写法。

 上一篇: Kotlin 基础05 - 标准函数、静态方法 下一篇: Kotlin 基础03 - 空指针检查机制 

© 2024 Homurax

UV: | PV:

Theme Typography by Makito

Proudly published with Hexo