Kotlin 高阶函数和协程

431 阅读6分钟

高阶函数

Lambda表达式: 一种匿名函数的简写形式

val myLambda: (String, Int) -> Unit = { s, i -> println("$s$i") }

注:多行lambda中,最后一行的表达式结果会自动作为整个lambda的返回值,无需额外声明。

unit 类似于void

 什么是it

  • it是Kotlin为单参数lambda表达式提供的默认参数名
  • 当lambda只有一个参数且未显式命名时,可以直接用it引用该参数
  • 这是Kotlin的一种语法糖,旨在减少样板代码
list.map { it.length }  // "it"自动代表列表元素

高阶函数:一种函数类型(以函数为参数或返回值)

高阶函数是指满足以下任一条件的函数:

  1. 接收一个或多个函数作为参数
  2. 返回一个函数
函数作为参数的高阶函数
// 定义高阶函数
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

// 使用高阶函数
val sum = calculate(10, 5) { x, y -> x + y }  // 结果为15
val product = calculate(10, 5) { x, y -> x * y }  // 结果为50
  • 当lambda作为最后一个参数时,可以移到括号外
  • 当lambda是唯一参数时,可以省略括号
  • it可用作默认参数名
函数作为返回值的高阶函数
// 定义返回函数的高阶函数
fun getOperation(type: String): (Int, Int) -> Int {
    return when (type) {
        "sum" -> { a, b -> a + b }
        "subtract" -> { a, b -> a - b }
        else -> { _, _ -> 0 }
    }
}


val operation = getOperation("sum")//返回加法函数
val result = operation(10, 5)  // 结果为15
特性Kotlin whenJava switch
支持类型任意类型(包括表达式、范围、类型检查)仅限整型、枚举、String(Java 14+)
返回值可作为表达式返回值仅语句,需额外变量赋值
智能转换支持(is 检查后自动转换类型)不支持
多条件匹配支持(逗号分隔或范围)需穿透(case 1: case 2:
when (x) {
    0, 1 -> "Binary digit"  // 匹配多个值
    in 2..10 -> "Between 2 and 10"  // 范围匹配
    !in 20..30 -> "Not in 20..30"   // 反向范围
    else -> "Other"
}

带状态的函数的高阶函数

fun createCounter(): () -> Int {
    var count = 0  // 被返回的函数捕获
    return { ++count }  // 每次调用递增count
}

fun main() {
    val counter = createCounter()
    println(counter())  // 输出: 1
    println(counter())  // 输出: 2
}

闭包的本质是 函数 + 其访问的外部变量环境。在 Kotlin 中,它通过 Lambda 或嵌套函数实现,常用于:

  1. 状态封装(如计数器、缓存)
  2. 回调处理(如事件监听)
  3. 函数式编程(如高阶函数返回带状态的函数)

扩展函数

扩展函数(Extension Functions)是 Kotlin 的核心特性之一,允许在不修改原类代码的情况下,为现有类添加新功能。它的本质是静态方法,但通过语法糖实现类成员式的调用。 扩展函数是静态解析的,不会实际插入到类中,编译后会转换为静态方法:

基本语法

// 为 String 类添加一个扩展函数
fun String.addExclamation(): String {
    return "$this!"  // this 指向接收者对象(String 实例)
}

// 调用
println("Hello".addExclamation())  // 输出: "Hello!"
fun <T> List<T>.printSize() {
    println("Size: ${this.size}")
}

listOf(1, 2, 3).printSize()  // 输出: "Size: 3"
// 扩展函数 + 高阶函数(Lambda 参数)
fun List<String>.filterBy(predicate: (String) -> Boolean): List<String> {
    return this.filter(predicate)
}

// 调用
val names = listOf("Alice", "Bob", "Charlie", "David")
val result = names.filterBy { it.length > 4 }  // 过滤长度 >4 的字符串
println(result)  // 输出: [Alice, Charlie, David]
fun <T> T.runTest(mm:T.()->Boolean)=mm();
123.runTest{
    println(this);
}

public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

  • String. :指定要扩展的类(接收者类型)。
  • this:在扩展函数内指向调用该函数的对象实例。

函数引用和函数类型转换

(1) 高阶函数 showAction
fun showAction(action: (Int) -> String) {
    println(action(88))  // 调用传入的函数,并打印结果
}
  • 接收一个函数参数 action,该函数接受 Int 返回 String
  • 调用时固定传入参数 88
(2) Lambda 实现 lambdaImpl
fun lambdaImpl(value: Int) = "我的值是:$value"
  • 普通函数,逻辑与匿名 Lambda 相同。

2. 三种调用方式对比

方式 1:直接传递 Lambda(匿名函数)
showAction {
    "我的值是:$it"  // `it` 是隐式参数名(单个参数时可用)
}
  • 特点:最简洁的写法,适用于简单逻辑。
方式 2:传递函数引用(:: 操作符)
showAction(::lambdaImpl)  // 将函数转为引用传递
  • 原理::lambdaImpl 将函数 lambdaImpl 转换为 (Int) -> String 类型的对象。
  • 优势:复用已有函数,避免重复代码。
方式 3:通过变量传递函数引用
val r1: Function1<Int, String> = ::lambdaImpl  // 显式指定函数类型接口
val r2: (Int) -> String = r1                  // 转换为 Kotlin 函数类型
val r3: Int.() -> String = r2                 // 转换为带接收者的函数类型

showAction(r3)  // 最终调用
  • 关键点
    • Function1<Int, String> 是 Kotlin 标准库中表示 (Int) -> String 的函数接口。
    • Int.() -> String带接收者的函数类型,等价于 (Int) -> String,但调用语法不同(见下文)。

3. 带接收者的函数类型(Int.() -> String

定义与调用
val receiverFunc: Int.() -> String = { "值:${this}" }  // `this` 指向接收者对象
println(100.receiverFunc())  // 输出: "值:100"(类似扩展函数)
  • 本质:第一个参数是接收者(Int),调用时可用 this 访问。
  • 转换关系
    Int.() -> String  ==  (Int) -> String
    Int.(String) -> Unit  ==  (Int, String) -> Unit
    
为什么能赋值给 (Int) -> String

Kotlin 允许带接收者的函数类型与非接收者类型隐式转换,因为它们的参数列表本质相同。


4. 代码执行流程

  1. ::lambdaImpl 将函数转为引用。
  2. 通过变量 r1r2r3 多次转换类型(最终仍是 (Int) -> String)。
  3. showAction(r3) 调用时,实际执行 lambdaImpl(88),输出:
    我的值是:88
    

5. 关键知识点总结

概念说明
函数引用 ::将命名函数转换为函数对象,可赋值给变量或传递。
函数类型转换Kotlin 支持不同表示法的函数类型隐式转换(如带接收者 ↔ 普通参数)。
Function1Kotlin 标准库中的函数接口,对应 (T) -> R 类型。
带接收者的函数类似扩展函数,Int.() -> Stringthis 指向接收者对象。

何时使用这种写法?

  1. 需要复用已有函数时(如 ::lambdaImpl)。
  2. 统一处理不同函数类型时(如将带接收者的函数传递给普通函数参数)。
  3. 反射或动态调用场景(通过引用获取函数元信息)。

Android开发中的常见应用

// 传统方式
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        // 处理点击
    }
})

// 使用高阶函数简化
fun View.onClick(action: (View) -> Unit) {
    setOnClickListener(action)
}

// 调用
button.onClick { 
    // 处理点击
}
定义扩展函数
fun View.onClick(action: (View) -> Unit) {
    setOnClickListener(action)
}

解析

  • View类添加扩展函数onClick
  • 参数action是一个函数类型:(View) -> Unit(接收View参数,返回Unit)
  • 内部直接调用原生setOnClickListener并传入这个lambda

优势

  • 将监听器设置抽象为更直观的"onClick"操作
  • 隐藏了接口实现的细节

批量点击事件中的高阶函数

class AndroidClickListener<T> {
    private val actions = arrayListOf<(T?) -> Unit>()  // 存储事件回调函数
    private val values = arrayListOf<T?>()             // 存储关联数据:保存每个事件关联的数据。

    // 添加监听器
    fun addListener(value: T?, action: (T?) -> Unit) {
        if (!actions.contains(action)) {
            actions.add(action)
            values.add(value)
        }
    }

    // 触发所有监听事件
    fun touchListeners() {
        if (actions.isEmpty()) {
            println("你还没有添加任何事件呀")
            return
        }
        actions.forEachIndexed { index, item ->
            item.invoke(values[index])  // 执行回调并传入关联值
        }
    }
}

fun main() {
    // 使用示例
    val clickHandler = AndroidClickListener<String>()
    
    // 第一次触发(无监听器)
    clickHandler.touchListeners()  // 输出: "你还没有添加任何事件呀"
    
    // 添加3个监听器
    clickHandler.addListener("事件1") { 
        println("触发事件1,值: $it") 
    }
    clickHandler.addListener("事件2") { 
        println("触发事件2,值: $it") 
    }
    clickHandler.addListener(null) { 
        println("触发事件3,值为null") 
    }
    
    // 第二次触发
    clickHandler.touchListeners() 
    /* 输出:
       触发事件1,值: 事件1
       触发事件2,值: 事件2
       触发事件3,值为null
    */
}

forEachIndexed

actions.forEachIndexed { index, item ->
    item.invoke(values[index])  // 执行回调并传入关联值
}
  • actions:一个存储着 Lambda 表达式(类型为 (T?) -> Unit)的集合。
  • forEachIndexed:Kotlin 标准库提供的扩展函数,用于遍历集合并同时获取索引和元素。
  • index:当前元素的索引(从 0 开始)。
  • item:当前元素(即 actions 中的 Lambda 表达式)。
  • item.invoke(values[index]) :调用 Lambda 并传入 values 集合中对应索引的值。
2. 关键概念解析
(1) forEachIndexed 函数
  • 作用:遍历集合,提供索引 + 元素的双参数 Lambda。

  • 等价于

    for ((index, item) in actions.withIndex()) {
        item.invoke(values[index])
    }
    
  • 优势:函数式风格,避免显式索引管理。

(2) invoke 方法
  • 本质item.invoke(values[index]) 等价于直接调用 item(values[index])
  • 意义:显式调用 Lambda 表达式,并传入参数(这里是 values[index])。
(3) 闭包访问
  • Lambda 内部通过 index 访问外部的 values 集合,这是 Kotlin 的闭包特性

操作符含义示例范围
..闭区间(包含两端)1..31, 2, 3
until半开区间(不含末端)1 until 31, 2
downTo递减区间3 downTo 13, 2, 1
step步长控制1..5 step 21, 3, 5

扩展函数takeIf 函数详解

takeIf 是 Kotlin 标准库中的一个非常有用的扩展函数,它允许你根据条件来决定是否接收一个值。下面我将详细介绍它的用法和实际应用场景。

基本语法
fun <T> T.takeIf(predicate: (T) -> Boolean): T?
工作原理

takeIf 函数会:

  1. 接收一个谓词(条件判断函数)
  2. 如果谓词对接收者对象(调用takeIf的对象)返回true,则返回该对象
  3. 如果谓词返回false,则返回null
简单示例
val number = 10
val result = number.takeIf { it > 5 }  // 返回10,因为10>5
println(result)  // 输出:10

val anotherResult = number.takeIf { it > 15 }  // 返回null,因为10不大于15
println(anotherResult)  // 输出:null
let 结合使用

takeIf 经常与 let 函数结合使用,形成强大的条件处理链:

val input = "Kotlin"
input.takeIf { it.length > 3 }?.let { 
    println("字符串长度大于3: $it") 
}
// 输出:字符串长度大于3: Kotlin
实际应用场景
1. 条件性对象处理
// 只有当用户不为null且是活跃用户时才发送消息
user?.takeIf { it.isActive }?.sendMessage()
2. 验证输入
fun processInput(input: String?) {
    input?.takeIf { it.isNotBlank() }?.let { 
        println("处理输入: $it") 
    } ?: println("输入无效")
}
3. 替代if语句
// 传统写法
if (file.exists() && file.canRead()) {
    file.readText()
}

// 使用takeIf的写法
file.takeIf { it.exists() && it.canRead() }?.readText()
takeUnless 对比

takeIf 有一个相反的函数 takeUnless,它在条件为false时返回对象:

val number = 10
val result = number.takeUnless { it > 5 }  // 返回null,因为10>5
性能考虑

takeIf 是内联函数(inline),不会带来额外的运行时开销,可以放心使用。


高阶

val names = listOf("Zhangsan", "Lisi", "Wangwu")  // 名字集合
val ages = listOf(20, 21, 22)                     // 年龄集合

names.zip(ages).map { (name, age) ->  // 直接解构 Pair
    "you name:$name, you age:$age" 
}
zip:合并两个集合为 Pair 列表
names.zip(ages)  // 输出: [("Zhangsan", 20), ("Lisi", 21), ("Wangwu", 22)]
  • 将 names 和 ages 按索引位置配对,生成 List<Pair<String, Int>>

Kotlin 标准库常用函数详解

1. 转换操作

  • map:一对一转换,保持结构。
  • flatMap:一对多展开,扁平化结果
val numbers = listOf(1, 2, 3)

// map - 转换每个元素
val doubled = numbers.map { it * 2 } // [2, 4, 6]

// flatMap - 转换并平铺
val flatResult = numbers.flatMap { listOf(it, it + 1) } // [1, 2, 2, 3, 3, 4]

// groupBy - 按条件分组
val grouped = numbers.groupBy { if (it % 2 == 0) "even" else "odd" }
// {"odd": [1, 3], "even": [2]}

如何优化大数据集的链式调用(如map().filter().map())?

1. 减少中间集合创建(核心优化)

每次链式调用(如 mapfilter)都会生成一个临时集合,大数据集时会导致内存和性能开销。

方案:使用 asSequence() 转换为序列
dataSet.asSequence()          // 转换为序列
    .map { transformA(it) }   // 惰性操作
    .filter { predicate(it) } // 惰性操作
    .map { transformB(it) }   // 惰性操作
    .toList()                 // 终端操作(最终生成结果)
  • 原理:序列(Sequence)是惰性求值的,仅在终端操作(如 toList())时执行计算,避免中间集合分配。

  • 性能对比

    // 列表操作:创建3个临时集合
    list.map{...}.filter{...}.map{...} 
    
    // 序列操作:仅1个最终集合
    list.asSequence().map{...}.filter{...}.map{...}.toList()
    
  • 适用场景:数据量 ≥ 1000 时效果显著。

注意
  • 小数据集(如 < 100 元素)直接使用集合操作更高效(序列有初始化开销)。
  • 避免在 asSequence() 后频繁调用 toList(),失去惰性优势。

2. 过滤操作

val numbers = listOf(1, 2, 3)
// filter - 条件过滤
val evens = numbers.filter { it % 2 == 0 } // [2]

// filterNot - 反向过滤
val notEvens = numbers.filterNot { it % 2 == 0 } // [1, 3]

// take/drop - 获取/丢弃前N个
val firstTwo = numbers.take(2) // [1, 2]
val afterFirst = numbers.drop(1) // [2, 3]

3. 聚合操作

val numbers = listOf(1, 2, 3)
// reduce - 累积操作
val sum = numbers.reduce { acc, num -> acc + num } // 6

// fold - 带初始值的累积
val sumWith10 = numbers.fold(10) { acc, num -> acc + num } // 16

// maxBy/minBy - 按条件找最值
val maxOdd = numbers.maxBy { it % 2 * it } // 3
  • reduce:简洁但要求非空集合,结果类型固定。

  • fold:灵活安全,支持初始值和类型转换。

  • 选择依据

    • 确定集合非空且结果类型相同 → reduce
    • 需要初始值、处理空集合或类型转换 → fold

二、作用域函数(Scope Functions)

Kotlin 作用域函数详解

Kotlin 标准库提供了几个作用域函数(Scope Functions),它们能在对象的上下文中执行代码块,主要区别在于:

  1. 如何访问上下文对象(thisit
  2. 返回值是什么(对象本身或 lambda 结果)

五大作用域函数对比

函数上下文对象引用返回值是否扩展函数典型使用场景
letitlambda 结果非空检查、数据转换
runthislambda 结果对象配置、计算值
withthislambda 结果对非空对象操作
applythis对象本身对象初始化
alsoit对象本身附加效果、链式调用

1. let 函数

特点

  • 通过 it 访问对象
  • 返回 lambda 表达式结果
  • 常用于空安全检查和数据转换
// 示例1:空安全检查
val name: String? = getName()
name?.let { 
    println(it.length) // it 是非空的name
}

// 示例2:数据转换
val length = user?.let { 
    it.name.length 
} ?: 0

2. run 函数

特点

  • 通过 this 访问对象(可省略)
  • 返回 lambda 表达式结果
  • 两种形式:扩展函数和非扩展函数
// 扩展函数形式
val result = user.run {
    println(name) // 直接访问属性
    calculateAge() // 最后一行作为返回值
}

// 非扩展函数形式(创建作用域)
val hexRegex = run {
    val digits = "0-9a-f"
    Regex("[$digits]+")
}

3. with 函数

特点

  • 非扩展函数
  • 通过 this 访问对象
  • 返回 lambda 表达式结果
  • 适合对已知非空对象操作
val user = getUser()
val description = with(user) {
    "Name: $name, Age: $age" // 直接访问属性
}

4. apply 函数

特点

  • 通过 this 访问对象
  • 返回对象本身
  • 主要用于对象初始化配置
val textView = TextView(context).apply {
    text = "Hello"
    textSize = 16f
    setTextColor(Color.BLACK)
}

5. also 函数

特点

  • 通过 it 访问对象
  • 返回对象本身
  • 适合执行附加操作
val file = File("data.txt").also {
    println("创建文件: ${it.path}")
    // 可以执行其他副作用操作
}.also {
    it.setReadable(true)
}

使用场景选择指南

  1. 初始化对象apply

    Intent().apply {
        action = "ACTION_VIEW"
        putExtra("key", value)
    }
    
  2. 数据转换letrun

    // 使用let
    val length = string?.let { it.length } ?: 0
    
    // 使用run
    val description = user.run { "$name-$age" }
    
  3. 需要作用域run(非扩展形式)

    val result = run {
        val x = calculateX()
        val y = calculateY()
        x + y
    }
    
  4. 链式调用添加操作also

    userList
        .filter { it.active }
        .also { log("找到${it.size}个活跃用户") }
        .map { it.name }
    
  5. 对已知非空对象操作with

    with(recyclerView) {
        layoutManager = LinearLayoutManager(context)
        adapter = MyAdapter()
    }
    

性能注意事项

  1. 所有作用域函数都是 inline 的,不会产生运行时开销
  2. 过度嵌套会降低可读性:
    // 不推荐
    user?.run {
        address?.let {
            street?.also {
                print(it.length)
            }
        }
    }
    

Android 开发实用示例

1. View 初始化

val button = Button(context).apply {
    layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
    text = "Submit"
    setOnClickListener { /* ... */ }
}

2. Intent 创建

val intent = Intent().apply {
    action = Intent.ACTION_SEND
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, message)
}

3. 资源释放

resource.use { 
    // 自动关闭资源
}.also {
    logger.log("资源已处理")
}

4. SharedPreferences 编辑

preferences.edit().apply {
    putString("key", value)
    putInt("count", 10)
    apply()
}

三、字符串处理

1. 模板与分割

// 字符串模板
val message = "User $name has ${items.size} items"

// 多行字符串
val sql = """
    SELECT * FROM users
    WHERE id = ?
    LIMIT 1
""".trimIndent()

// 分割操作
val parts = "a,b,c".split(",") // ["a", "b", "c"]

2. 常用扩展

// 判空/空白
val isBlank = string.isBlank()

// 截取与移除
val withoutPrefix = string.removePrefix("http://")
val substring = string.substring(1..3)

// 正则匹配
val hasDigits = string.contains(Regex("\\d+"))

四、IO与文件操作

1. 文件读写

// 读取文件
val content = File("path.txt").readText()

// 写入文件
File("output.txt").writeText("Hello")

// 按行处理
File("data.csv").forEachLine { line ->
    process(line)
}

2. 流处理

// 自动关闭资源
FileInputStream("file").use { stream ->
    // 操作stream
}

// 缓冲读取
File("large.txt").bufferedReader().use { reader ->
    reader.readLine()
}

六、其他实用函数

1. 空安全操作

// 空合并操作符
val name = user?.name ?: "Unknown"

// 安全转换
val number = input as? Int // 失败返回null

2. 区间与序列

// 区间迭代
for (i in 1..10 step 2) { ... }

// 序列生成
val sequence = generateSequence(1) { it * 2 }
sequence.take(5).toList() // [1, 2, 4, 8, 16]

3. 类型检查

when (obj) {
    is String -> obj.length
    is Int -> obj + 1
    else -> 0
}

Android开发特别推荐

1. 视图绑定简化

fun <T : View> View.bindView(@IdRes id: Int): Lazy<T> = lazy {
    findViewById(id) as T
}

// 使用
private val button by bindView<Button>(R.id.btn_submit)

协程

协程和线程的区别

线程

  • 抢占式调度:操作系统决定何时切换线程
  • 并行执行:多核CPU上真正同时运行
  • 阻塞操作会占用整个线程资源

协程

  • 协作式调度:协程主动让出执行权
  • 挂起(suspend)而非阻塞:遇到IO操作时挂起,释放底层线程
  • 单线程上可并发执行多个协程