kotlin 闭包

在 Kotlin 中,闭包(Closure) 是一个核心的函数式编程概念,简单来说:闭包是能捕获并访问其外部作用域变量的函数(包括 lambda 表达式、匿名函数、局部函数),即使这个函数在外部作用域之外执行,也能持有这些变量的引用

一、核心理解:闭包的本质

可以把闭包想象成一个 “带背包的函数”—— 函数在定义时,会把它所在环境的变量 “装进背包”,之后无论这个函数走到哪里(比如被传递到其他地方执行),都能从背包里取出这些变量使用,甚至修改它们。

1. 基础示例:捕获外部变量
最直观的例子是 lambda 表达式捕获外部变量:

fun main() {
    // 外部作用域的变量
    var count = 0

    // 定义一个闭包(lambda 表达式),捕获并修改外部变量 count
    val increment: () -> Unit = {
        count++ // 访问外部作用域的变量,这就是闭包的核心
        println("当前count:$count")
    }

    // 执行闭包,即使在外部作用域内执行,也体现了捕获特性
    increment() // 输出:当前count:1
    increment() // 输出:当前count:2

    // 外部变量已被闭包修改
    println("外部count:$count") // 输出:外部count:2
}

关键解释

  • increment 这个 lambda 是一个闭包,它捕获了外部作用域的 count 变量。
  • 闭包不仅能读取外部变量,还能修改它(因为 countvar),且修改会反映到外部作用域。

2. 进阶示例:闭包脱离原作用域执行
闭包的核心能力是 “脱离原作用域仍能访问外部变量”,比如把闭包作为返回值传递到其他地方:

// 定义一个函数,返回一个闭包
fun createCounter(initial: Int): () -> Int {
    var current = initial // 函数内部的局部变量

    // 返回一个 lambda(闭包),捕获 current
    return {
        current++ // 即使 createCounter 执行完毕,闭包仍持有 current 的引用
    }
}

fun main() {
    // 创建第一个计数器,初始值 0
    val counter1 = createCounter(0)
    println(counter1()) // 输出:1
    println(counter1()) // 输出:2

    // 创建第二个计数器,初始值 10
    val counter2 = createCounter(10)
    println(counter2()) // 输出:11
    println(counter1()) // 输出:3(counter1 的 current 独立)
}

关键解释

  • createCounter 执行完毕后,其内部的 current 变量本应被销毁,但闭包(返回的 lambda)持有它的引用,因此 current 会一直存在。
  • 每个闭包实例(counter1counter2)都有自己独立的 current 变量,互不干扰。

二、闭包的常见使用场景

1. 简化回调逻辑
替代传统的接口回调,用闭包捕获上下文变量,避免额外的类定义:

// 模拟网络请求函数,接收一个成功回调的闭包
fun requestData(url: String, onSuccess: (String) -> Unit) {
    // 模拟网络请求结果
    val data = "从 $url 获取的数据"
    // 执行回调(闭包)
    onSuccess(data)
}

fun main() {
    val userId = 1001 // 外部变量
    // 调用请求函数,闭包捕获 userId
    requestData("https://api/user") { data ->
        println("用户 $userId 的数据:$data") // 输出:用户 1001 的数据:从 https://api/user 获取的数据
    }
}

2. 函数式编程(集合操作)
Kotlin 集合的 filtermapforEach 等函数的参数都是闭包,能捕获外部变量:

fun main() {
    val threshold = 5 // 外部阈值
    val numbers = listOf(1, 3, 6, 8, 4)

    // filter 的 lambda 是闭包,捕获 threshold
    val filtered = numbers.filter { it > threshold }
    println(filtered) // 输出:[6, 8]
}

3. 局部函数(也是闭包)
Kotlin 的局部函数(定义在函数内部的函数)同样是闭包,能访问外部函数的变量:

fun calculateTotal(prices: List<Double>): Double {
    val taxRate = 0.08 // 外部变量

    // 局部函数(闭包),捕获 taxRate
    fun calculateTax(price: Double): Double {
        return price * taxRate
    }

    var total = 0.0
    prices.forEach {
        total += it + calculateTax(it)
    }
    return total
}

fun main() {
    val prices = listOf(100.0, 200.0)
    println(calculateTotal(prices)) // 输出:324.0(100*1.08 + 200*1.08)
}

三、闭包的注意事项

  1. 变量捕获的规则

    • 捕获 val(不可变变量):闭包会持有其值的副本(或引用,取决于类型)。
    • 捕获 var(可变变量):Kotlin 会把 var 包装成一个内部类(Ref),闭包持有这个包装类的引用,因此能修改外部变量。
  2. 内存泄漏风险:如果闭包被长期持有(比如作为回调传入生命周期长的对象),且捕获了 Activity/Fragment 等对象,可能导致内存泄漏,需注意及时释放或使用弱引用。

  3. 非局部返回:在闭包中使用 return 时,默认会直接返回外层函数(非局部返回),如果只想返回闭包本身,需用 return@标签

fun test() {
    listOf(1,2,3).forEach {
        if (it == 2) return@forEach // 只退出 forEach 的当前迭代,不退出 test 函数
        println(it)
    }
    println("函数结束")
}
// 输出:1 3 函数结束

总结

  1. Kotlin 闭包是能捕获并访问外部作用域变量的函数(lambda / 匿名函数 / 局部函数),即使函数脱离原作用域执行,仍能持有变量引用。
  2. 闭包可修改外部可变变量(var),且每个闭包实例有独立的变量上下文,互不干扰。
  3. 闭包是 Kotlin 函数式编程的核心,广泛用于集合操作、回调逻辑、局部函数等场景,需注意内存泄漏和非局部返回的问题。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容