当前位置: 首页 > article >正文

深入了解 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]
	}

总结

通过自定义高阶函数,我们可以编写更灵活的代码,减少重复,增强代码的可读性和复用性。


http://www.kler.cn/a/379561.html

相关文章:

  • nginx代理云数据库链接实现办公室内网访问云上内网数据库
  • IntelliJ IDEA使用技巧与插件推荐
  • mac-泛洪
  • Linux中断、软中断、MMU内存映射-深入理解
  • dc源码铺子应用部署教程
  • 服务器新建用户
  • SpringBoot实现:高效在线试题库系统
  • koa + sequelize做距离计算(MySql篇)
  • 使用WordPress快速搭建个人网站
  • 汽车电子行业数字化转型的实践与探索——以盈趣汽车电子为例
  • Python酷库之旅-第三方库Pandas(193)
  • 【工具变量】中国制造2025试点城市数据集(2000-2023年)
  • Maven核心概念
  • Linux-计算机网络-epoll的LT,ET模式
  • 力扣150:逆波兰表达式求值
  • 使用Web Workers实现JavaScript的多线程编程
  • 【WebRTC】WebRTC的简单使用
  • 【嵌入式面试高频知识点】-MQTT协议
  • 【appium 安卓10 QQ发送消息】
  • 不用买PSP,画质甚至更好,这款免费神器让你玩遍经典游戏
  • 基于卷积神经网络的棉花病虫害识别与防治系统,resnet50,mobilenet模型【pytorch框架+python源码】
  • Spring的常用注解之@Component——day1
  • 【Keyframes】Deep Convolutional Pooling Transformer for Deepfake Detection
  • 【VMware】使用笔记
  • STL:标准模板库
  • Ubuntu 22.4 LTS 源码编译Tigervnc