深入了解 Kotlin 高阶函数
文章目录
- 前言
- 一、什么是高阶函数?
- 二、常用高阶函数
- 1、map :将集合中的每个元素应用转换函数,返回一个新的集合。
- 1. 转换数据类型
- 2. 复杂对象映射
- 3. 使用索引的映射
- 4. 延迟计算的 map
- 5. flatMap:多层映射
- 总结
- 2、filter:根据条件过滤集合,返回满足条件的元素。
- 1. 根据条件过滤集合
- 2. 过滤 null 值
- 3. 使用索引的过滤
- 4. 反向过滤
- 5. 惰性计算的 filter
- 6. partition:分割集合
- 总结
- 3、reduce:将集合中的所有元素结合成一个结果,使用指定的操作。
- 1. 计算乘积
- 2. 字符串连接
- 3. 找到集合中的最大或最小值
- 4. 惰性计算与序列
- 总结
- 4、fold:类似于 reduce,但可以指定初始值。
- 1. 计算和或乘积(带初始值)
- 2. 字符串连接
- 3. 统计字符出现的频率
- 4. 自定义初始累积结果
- 5. foldRight 的使用
- 总结
- 5、reduce 与 fold 的区别
- 惰性计算与序列
- 总结
- 6、run:在对象上下文中执行代码块,并返回代码块的结果。
- 1. 对象初始化
- 2. 非空检查
- 3. 链式调用
- 4. 表达式返回值
- 5. 顶层 run
- 6. run 与其他作用域函数对比
- 7. 总结
- 7、自定义高阶函数
- 1. 自定义一个高阶函数
- 2. 自定义返回函数的高阶函数
- 3. 结合 Lambda 表达式与高阶函数
- 4. 自定义集合操作
- 5. 组合高阶函数
- 总结
前言
在 Kotlin 中,高阶函数是一个重要的概念,它极大地增强了语言的灵活性和表达能力。高阶函数允许将函数作为参数传递或返回,从而使得代码更加简洁和易于维护。
本文将介绍 Kotlin 的高阶函数,包括基本概念、常用函数及其应用示例。
一、什么是高阶函数?
高阶函数是指满足以下任一条件的函数:
1、接收一个或多个函数作为参数。
2、返回一个函数。
这种特性使得 Kotlin 在处理集合、异步编程和事件处理等方面具有很大的优势。
例如:
fun operation(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
fun add(x: Int, y: Int) = x + y
fun subtract(x: Int, y: Int) = x - y
fun main() {
println(operation(5, 3, ::add)) // 输出: 8
println(operation(5, 3, ::subtract)) // 输出: 2
}
二、常用高阶函数
1、map :将集合中的每个元素应用转换函数,返回一个新的集合。
基本用法
map 函数将每个元素应用给定的转换函数,然后将转换后的结果放入一个新集合中,返回该新集合。
语法:
fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>
- T:原集合中元素的类型。
- R:转换后元素的类型。
- transform:一个高阶函数,将集合的每个元素 T 转换为类型 R 的新元素。
示例:
fun main() {
val numbers = listOf(1, 2, 3)
val doubled = numbers.map { it * 2 }
println("$doubled") //输出: [2, 4, 6]
}
常见的使用场景
1. 转换数据类型
map 不仅可以改变集合的值,还可以转换数据类型。例如,将整数列表转换为字符串列表
示例
val numbers = listOf(1, 2, 3, 4)
val stringNumbers = numbers.map { it.toString() }
println(stringNumbers) // 输出: ["1", "2", "3", "4"]
\
2. 复杂对象映射
map 在处理对象集合时也非常有用。例如,将一个用户对象集合映射为用户名称列表:
示例
data class User(val name: String, val age: Int)
val users = listOf(User("Jack", 25), User("Lucy", 30))
val names = users.map { it.name }
println(names) // 输出: ["Jack", "Lucy"]
\
3. 使用索引的映射
示例
val words = listOf("a", "b", "c")
val indexedWords = words.mapIndexed { index, word -> "$index: $word" }
println(indexedWords) // 输出: ["0: a", "1: b", "2: c"]
4. 延迟计算的 map
如果要处理的数据集合较大,可以使用 asSequence() 将集合转换为惰性序列(惰性序列:避免避免一次性加载所有数据)。
序列中的 map 操作会延迟计算,只在最后需要结果时才执行全部映射,这样可以提升性能。
示例
val numbers = (1..1_000_000).asSequence()
val squared = numbers.map { it * it }.take(5).toList()
println(squared) // 输出: [1, 4, 9, 16, 25]
\
5. flatMap:多层映射
当需要将集合中的每个元素转换成多个元素时,可以使用 flatMap。它会对集合的每个元素生成一个集合,并将所有结果平铺为一个集合。
示例:
val words = listOf("Hello", "World")
val chars = words.flatMap { it.toList() }
println(chars) // 输出: [H, e, l, l, o, W, o, r, l, d]
总结
- map 是 Kotlin 中用于集合转换的强大工具。
- mapIndexed 可以获取元素索引。
- 使用 asSequence() 可以延迟计算,提升性能。
- flatMap 则适用于多层级映射,将多个结果合并到一个集合中。
2、filter:根据条件过滤集合,返回满足条件的元素。
基本用法
filter 会对集合中的每个元素应用一个条件(即谓词函数),保留那些返回 true 的元素,返回一个新的集合。
语法:
fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>
- T:集合元素的类型。
- predicate:一个高阶函数,传入一个元素并返回 Boolean 值,表示该元素是否满足条件。
示例:
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // 输出: [2, 4]
常见的使用场景
\
1. 根据条件过滤集合
filter 可以用于各种条件的筛选,例如从字符串列表中筛选出特定长度的字符串:
示例:
val words = listOf("Kotlin", "is", "fun")
val longWords = words.filter { it.length > 3 }
println(longWords) // 输出: ["Kotlin", "fun"]
\
2. 过滤 null 值
在包含 null 的集合中,使用 filterNotNull 可以过滤掉所有 null 值:
示例:
val numbersWithNulls = listOf(1, null, 2, null, 3)
val nonNullNumbers = numbersWithNulls.filterNotNull()
println(nonNullNumbers) // 输出: [1, 2, 3]
3. 使用索引的过滤
如果你需要利用元素的索引,可以使用 filterIndexed,它提供元素的索引值供使用:
示例:
val numbers = listOf(1, 2, 3, 4, 5)
val oddIndexedNumbers = numbers.filterIndexed { index, _ -> index % 2 == 1 }
println(oddIndexedNumbers) // 输出: [2, 4]
4. 反向过滤
希望筛选不符合某个条件的元素,filterNot 可以实现这一点:
示例:
val numbers = listOf(1, 2, 3, 4, 5)
val oddNumbers = numbers.filterNot { it % 2 == 0 }
println(oddNumbers) // 输出: [1, 3, 5]
5. 惰性计算的 filter
对于大数据集合,可以将集合转换为序列(使用 asSequence()),然后在序列上应用 filter。这种方式是惰性计算的,仅在最后需要结果时才执行,性能更高:
示例:
val numbers = (1..1_000_000).asSequence()
val largeNumbers = numbers.filter { it > 999_995 }.toList()
println(largeNumbers) // 输出: [999996, 999997, 999998, 999999, 1000000]
6. partition:分割集合
partition 是 filter 的一种变体,返回两个集合
第一个集合包含所有满足条件的元素
第二个集合包含不满足条件的元素
val numbers = listOf(1, 2, 3, 4, 5)
val (even, odd) = numbers.partition { it % 2 == 0 }
println(even) // 输出: [2, 4]
println(odd) // 输出: [1, 3, 5]
总结
- filter:筛选满足条件的元素。
- filterNotNull:过滤 null 值。
- filterIndexed:根据索引和条件筛选元素。
- filterNot:保留不满足条件的元素。
- partition:将集合分割为满足条件和不满足条件的两个集合。
3、reduce:将集合中的所有元素结合成一个结果,使用指定的操作。
基本用法
reduce 函数会将集合中的第一个元素作为初始值,依次应用给定的操作,将上一个累积的结果与当前元素组合,直到遍历完所有元素。
示例:
val numbers = listOf(1, 2, 3)
val reduceNumbers = numbers.reduce { acc, number -> acc + number } // 6
println("$reduceNumbers") // 6
语法:
fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S
- acc:累积值,代表之前所有元素的累积结果。
- T:集合中元素的类型。
- operation:一个高阶函数,指定累积的操作。
常见的使用场景
1. 计算乘积
除了计算和,也可以使用 reduce 来计算集合的乘积:
示例:
val numbers = listOf(1, 2, 3, 4)
val product = numbers.reduce { acc, num -> acc * num }
println(product) // 输出: 24
2. 字符串连接
示例:
reduce 可以用于将字符串列表连接成一个完整的字符串:
val words = listOf("Kotlin", "is", "Good")
val sentence = words.reduce { acc, word -> "$acc $word" }
println(sentence) // 输出: Kotlin is Good
3. 找到集合中的最大或最小值
通过比较每个元素,可以使用 reduce 找到集合中的最大值或最小值:
示例:
val numbers = listOf(3, 5, 4, 8, 2)
val max = numbers.reduce { acc, num -> if (acc > num) acc else num }
println(max) // 输出: 8
4. 惰性计算与序列
在大数据集合中,可以将集合转换为序列,使用 reduce 和 fold 进行惰性计算,只在最后需要结果时才进行所有操作。
示例:
val numbers = (1..1_000_000).asSequence()
val sum = numbers.reduce { acc, num -> acc + num }
println(sum)
注意事项 : reduce 必须作用于非空集合**
总结
- reduce:用于将集合中的元素合并成单一结果。
- reduceOrNull:适用于可能为空的集合。
- 惰性计算:可以在大数据集合上使用 asSequence() 提高性能。
4、fold:类似于 reduce,但可以指定初始值。
基本用法
fold 会遍历集合的每一个元素,将累计的结果与当前元素通过指定的操作组合起来,生成一个最终结果。
语法:
fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R
- initial:初始值,在累积过程中作为第一个累积值参与计算。
- acc:累积值,代表之前所有元素的累积结果。
- T:集合元素的类型。
- R:累积值的类型,即最终返回的结果类型。
- operation:一个高阶函数,用于定义累积过程。
示例
val numbers = listOf(1, 2, 3, 4)
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum) // 输出: 10
常见适用场景
1. 计算和或乘积(带初始值)
由于 fold 支持初始值,我们可以直接设置累积的起始值来计算和或乘积。例如,带初始值 10 的和:
示例:
val numbers = listOf(1, 2, 3, 4)
val sumWithInitial = numbers.fold(10) { acc, num -> acc + num }
println(sumWithInitial) // 输出: 20
2. 字符串连接
使用 fold 可以很方便地将集合元素连接为一个字符串,并且可以指定一个初始字符串。
示例:
val words = listOf("Kotlin", "is", "awesome")
val sentence = words.fold("Learning:") { acc, word -> "$acc $word" }
println(sentence) // 输出: Learning: Kotlin is awesome
3. 统计字符出现的频率
使用 fold 可以实现统计字符频率的功能,最终结果可以是一个 Map,记录每个字符出现的次数:
示例:
val chars = listOf('a', 'b', 'a', 'c', 'a', 'b')
val frequency = chars.fold(mutableMapOf<Char, Int>()) { acc, char ->
acc[char] = acc.getOrDefault(char, 0) + 1
acc
}
println(frequency) // 输出: {a=3, b=2, c=1}
4. 自定义初始累积结果
fold 的灵活性也体现在可以让累积结果类型和集合元素类型不同。例如,计算字符长度的总和:
示例:
val words = listOf("Kotlin", "is", "great")
val totalLength = words.fold(0) { acc, word -> acc + word.length }
println(totalLength) // 输出: 13
5. foldRight 的使用
除了 fold,Kotlin 还提供了 foldRight 函数,从集合的末尾向前进行累计操作。foldRight 的用法和 fold 类似,但累计顺序不同,适合一些依赖于顺序的累积过程。
示例:
val words = listOf("Kotlin", "is", "awesome")
val reversedSentence = words.foldRight("End") { word, acc -> "$word $acc" }
println(reversedSentence) // 输出: Kotlin is awesome End
总结
- fold:用于累积集合的结果,支持指定初始值。
- foldRight:从右向左累计,适合逆向累积。
- 灵活的累积类型:可以将累积结果类型与集合元素类型不同。
5、reduce 与 fold 的区别
reduce 示例
val numbers = listOf(1, 2, 3, 4)
val sum = numbers.reduce { acc, num -> acc + num }
println(sum) // 输出: 10
fold 示例
val numbers = listOf(1, 2, 3, 4)
val sumWithInitial = numbers.fold(10) { acc, num -> acc + num }
println(sumWithInitial) // 输出: 20
惰性计算与序列
在大数据集合中,可以使用 asSequence() 将集合转化为惰性序列,并在序列上使用 fold 进行延迟计算,提高性能:
示例:
val numbers = (1..1_000_000).asSequence()
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum)
总结
- fold:允许指定初始值,适用于需要额外数据参与计算的情况。
- reduce:直接以第一个元素为初始值,不允许自定义初始值,适用于简单的累积操作。
- 惰性计算:可以在序列上使用,提高性能。
6、run:在对象上下文中执行代码块,并返回代码块的结果。
run 常用于避免重复引用对象、初始化对象、以及简化代码逻辑
基本用法
fun <T, R> T.run(block: T.() -> R): R
- T:接收者对象的类型,即调用 run 的对象类型。
- R:返回值类型,表示代码块执行后的结果。
- block:一个带接收者的 lambda 表达式(即在该代码块中直接引用调用对象),返回类型为 R。
run 会在调用对象上执行 block,并返回 block 的结果。
示例
val result = "Hello".run {
"$this World"
}
println(result) // "Hello World"
常见使用场景
1. 对象初始化
run 常用于初始化对象,并在初始化后立即执行某些操作。比如,在创建一个 Person 对象时设置属性:
示例:
data class Person(var name: String, var age: Int)
val person = Person("John", 20).run {
age += 5 // 修改对象的属性
this // 返回修改后的对象
}
println(person) // 输出: Person(name=John, age=25)
//这里 run 对 Person 对象进行操作,并返回修改后的对象本身。
2. 非空检查
run 常用于处理可能为 null 的对象。利用安全调用操作符 ?.run,可以避免手动检查 null:
示例:
val name: String? = "Kotlin"
val length = name?.run {
this.length
} ?: 0
println(length) // 输出: 6
//当 name 不为 null 时才会调用 run,否则返回默认值 0。
3. 链式调用
通过 run 可以避免重复对象引用,尤其在对同一个对象调用多个方法时,可以使代码更简洁:
示例:
//run 简化了链式调用的代码
val text = " Kotlin is Fun "
val result = text.run {
trim() // 去除首尾空格
.uppercase() // 转为大写
.replace(" ", "_") // 替换空格
}
println(result) // 输出: KOTLIN_IS_FUN
4. 表达式返回值
run 可以将代码块的最后一个表达式作为返回值,用于一些需要返回单一结果的计算:
示例:
val radius = 6
val area = run {
val pi = 3.14
pi * radius * radius // 返回值
}
println(area) // 输出: 113.04
5. 顶层 run
在不依赖任何对象的情况下,run 也可以作为顶层函数使用,适合执行一段逻辑并返回结果:
示例:
val result = run {
val a = 10
val b = 30
a + b // 返回结果
}
println(result) // 输出: 40
6. run 与其他作用域函数对比
Kotlin 提供了多个作用域函数(run, let, apply, also, with),每个函数的用途略有不同:
- run:在接收者对象上执行代码块并返回结果,适合处理空对象和链式调用。
- let:把调用对象作为参数传递给代码块,适合在非空情况下执行代码。
- apply:在对象上执行代码块,返回对象本身,适合对象配置。
- also:把调用对象作为参数传递给代码块,返回对象本身,适合调试日志或链式调用。
- with:在对象上执行代码块,但不是扩展函数,需要显式传入对象。
7. 总结
- run 是一个灵活的高阶函数,适用于对象初始化、非空检查、链式调用等场景。
- 支持顶层调用,可以用来执行一段逻辑并返回结果。
- 在对象上调用时,run 将代码块中的最后一个表达式作为返回值,适合需要返回单一结果的操作。
7、自定义高阶函数
高阶函数是指将一个或多个函数作为参数,或者返回一个函数的函数。借助高阶函数,可以实现函数式编程风格的各种操作,如自定义过滤、排序、转换等。
1. 自定义一个高阶函数
示例:
例如对两个整数进行运算。可以将操作作为一个函数参数传递给高阶函数来实现。
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
calculate 是一个高阶函数,接收两个整数 a 和 b,以及一个函数 operation。operation 的类型是 (Int, Int) -> Int,表示它接收两个 Int 参数并返回一个 Int 类型的结果。
我们可以通过不同的操作来调用 calculate:
fun main() {
val sum = calculate(5, 3) { x, y -> x + y }
println("Sum: $sum") // 输出: Sum: 8
val product = calculate(5, 3) { x, y -> x * y }
println("Product: $product") // 输出: Product: 15
}
在调用 calculate 时,我们将操作的逻辑直接传递给 operation 参数,从而实现了不同的计算方式。
2. 自定义返回函数的高阶函数
Kotlin 允许我们定义一个返回函数的高阶函数。这在构建可以根据不同条件生成特定行为的函数时非常有用。
示例:
fun getOperation(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"multiply" -> { a, b -> a * b }
else -> { _, _ -> 0 }
}
}
getOperation 根据 type 返回不同的操作函数:如果 type 是 “add”,返回一个相加的函数;如果是 “multiply”,则返回一个相乘的函数。
使用 getOperation
fun main() {
val addOperation = getOperation("add")
println(addOperation(5, 3)) // 输出: 8
val multiplyOperation = getOperation("multiply")
println(multiplyOperation(5, 3)) // 输出: 15
}
3. 结合 Lambda 表达式与高阶函数
在 Kotlin 中可以使用 Lambda 表达式与高阶函数结合,进一步提升代码的简洁性和灵活性。比如,我们可以创建一个 applyOperation 函数来执行一个条件下的操作。
示例:条件操作的高阶函数
fun applyOperationIf(condition: Boolean, operation: () -> Unit) {
if (condition) {
operation()
}
}
applyOperationIf 是一个高阶函数,如果 condition 为 true,则执行 operation。
使用 applyOperationIf
fun main() {
val shouldExecute = true
applyOperationIf(shouldExecute) {
println("Operation executed!")
}
// 输出: Operation executed!
}
4. 自定义集合操作
可以利用高阶函数编写一些常见的集合操作函数,比如过滤、映射等,增强集合的灵活性。
fun <T> List<T>.customFilter(predicate: (T) -> Boolean): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) {
result.add(item)
}
}
return result
}
customFilter 是一个扩展函数,用于自定义集合的过滤。它接受一个条件函数 predicate,并返回满足条件的元素列表。
使用 customFilter
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val evenNumbers = numbers.customFilter { it % 2 == 0 }
println(evenNumbers) // 输出: [2, 4]
}
5. 组合高阶函数
Kotlin 中的高阶函数可以灵活组合,创建更复杂的逻辑。例如,我们可以创建一个多重条件过滤器。
示例:多重条件过滤器
fun <T> List<T>.filterWithConditions(vararg predicates: (T) -> Boolean): List<T> {
return this.filter { item ->
predicates.all { predicate -> predicate(item) }
}
}
在这里,filterWithConditions 接受一个或多个条件函数,并返回满足所有条件的元素。
使用 filterWithConditions
fun main() {
val numbers = listOf(10, 15, 20, 25, 30)
val result = numbers.filterWithConditions(
{ it > 10 },
{ it % 2 == 0 }
)
println(result) // 输出: [20, 30]
}
总结
通过自定义高阶函数,我们可以编写更灵活的代码,减少重复,增强代码的可读性和复用性。