在 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变量。 - 闭包不仅能读取外部变量,还能修改它(因为
count是var),且修改会反映到外部作用域。
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会一直存在。 - 每个闭包实例(
counter1、counter2)都有自己独立的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 集合的 filter、map、forEach 等函数的参数都是闭包,能捕获外部变量:
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)
}
三、闭包的注意事项
-
变量捕获的规则:
- 捕获
val(不可变变量):闭包会持有其值的副本(或引用,取决于类型)。 - 捕获
var(可变变量):Kotlin 会把var包装成一个内部类(Ref),闭包持有这个包装类的引用,因此能修改外部变量。
- 捕获
内存泄漏风险:如果闭包被长期持有(比如作为回调传入生命周期长的对象),且捕获了 Activity/Fragment 等对象,可能导致内存泄漏,需注意及时释放或使用弱引用。
非局部返回:在闭包中使用
return时,默认会直接返回外层函数(非局部返回),如果只想返回闭包本身,需用return@标签:
fun test() {
listOf(1,2,3).forEach {
if (it == 2) return@forEach // 只退出 forEach 的当前迭代,不退出 test 函数
println(it)
}
println("函数结束")
}
// 输出:1 3 函数结束
总结
- Kotlin 闭包是能捕获并访问外部作用域变量的函数(lambda / 匿名函数 / 局部函数),即使函数脱离原作用域执行,仍能持有变量引用。
- 闭包可修改外部可变变量(
var),且每个闭包实例有独立的变量上下文,互不干扰。 - 闭包是 Kotlin 函数式编程的核心,广泛用于集合操作、回调逻辑、局部函数等场景,需注意内存泄漏和非局部返回的问题。