Kotlin基础知识点 #122:Lambda表达式详解

难度:⭐⭐

🎯 问题

在Android开发中编写点击监听器、线程回调时,Java的匿名内部类代码冗长。Kotlin的Lambda表达式如何简化这些场景?有哪些语法糖和最佳实践?

💡 核心概念

Lambda表达式是匿名函数的简洁表示法,可以作为值传递。Kotlin的Lambda表达式比Java 8更强大,支持多种语法糖和智能类型推断。

Lambda语法结构

{ 参数列表 -> 函数体 }

代码示例

示例1:Lambda基础语法

// 完整语法
val sum: (Int, Int) -> Int = { a: Int, b: Int -> a + b }

// 类型推断:省略参数类型
val sum2: (Int, Int) -> Int = { a, b -> a + b }

// 类型推断:省略变量类型
val sum3 = { a: Int, b: Int -> a + b }

// 单个参数:使用it
val double: (Int) -> Int = { it * 2 }

// 无参数
val greet: () -> String = { "Hello, Kotlin!" }

// 多行Lambda:最后一行作为返回值
val complexCalc = { a: Int, b: Int ->
    val temp = a + b
    val result = temp * 2
    result  // 返回值
}

// Android场景:按钮点击
button.setOnClickListener { view ->
    Toast.makeText(this, "点击了按钮", Toast.LENGTH_SHORT).show()
}

// 简化:不使用参数
button.setOnClickListener {
    Toast.makeText(this, "点击了按钮", Toast.LENGTH_SHORT).show()
}

示例2:Lambda作为最后参数(尾随Lambda)

// 标准库函数
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
}

// 使用方式对比
fun testTrailingLambda() {
    val numbers = listOf(1, 2, 3, 4, 5)

    // ❌ 不推荐:Lambda在括号内
    val evens1 = numbers.customFilter({ it % 2 == 0 })

    // ✅ 推荐:尾随Lambda(括号外)
    val evens2 = numbers.customFilter { it % 2 == 0 }

    // 唯一参数是Lambda时,括号可省略
    val doubled = numbers.map { it * 2 }
}

// Android场景:协程启动
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 尾随Lambda:简洁的异步代码
        lifecycleScope.launch {
            val data = fetchDataFromNetwork()
            updateUI(data)
        }
    }

    private suspend fun fetchDataFromNetwork(): String {
        delay(1000)
        return "Network Data"
    }

    private fun updateUI(data: String) {
        // 更新UI
    }
}

示例3:Lambda中的return和标签

// 问题:Lambda中的return行为
fun testReturn() {
    val numbers = listOf(1, 2, 3, 4, 5)

    // ❌ 错误:会导致整个testReturn函数返回
    // numbers.forEach {
    //     if (it == 3) return  // 非局部返回
    //     println(it)
    // }

    // ✅ 方式1:使用标签返回(局部返回)
    numbers.forEach label@{
        if (it == 3) return@label  // 只退出当前Lambda
        println(it)
    }
    // 输出: 1, 2, 4, 5

    // ✅ 方式2:使用隐式标签(函数名)
    numbers.forEach {
        if (it == 3) return@forEach
        println(it)
    }

    // ✅ 方式3:使用匿名函数(不是Lambda)
    numbers.forEach(fun(value: Int) {
        if (value == 3) return  // 只退出匿名函数
        println(value)
    })

    println("函数继续执行")
}

// Android场景:列表过滤处理
class DeviceListFragment : Fragment() {
    private fun processDevices(devices: List<Device>) {
        devices.forEach { device ->
            // 跳过离线设备
            if (!device.isOnline) return@forEach

            // 处理在线设备
            updateDeviceStatus(device)
        }

        // 所有设备处理完成
        showCompleteMessage()
    }

    private fun updateDeviceStatus(device: Device) {}
    private fun showCompleteMessage() {}
}

data class Device(val id: String, val isOnline: Boolean)

示例4:Lambda接收者(带接收者的Lambda)

// 普通Lambda vs 带接收者的Lambda
fun buildString1(action: (StringBuilder) -> Unit): String {
    val sb = StringBuilder()
    action(sb)  // 传递sb作为参数
    return sb.toString()
}

fun buildString2(action: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.action()  // sb作为接收者
    return sb.toString()
}

// 使用对比
fun testReceiverLambda() {
    // 普通Lambda:需要显式使用参数
    val result1 = buildString1 { sb ->
        sb.append("Hello")
        sb.append(" ")
        sb.append("World")
    }

    // 带接收者的Lambda:直接调用StringBuilder方法
    val result2 = buildString2 {
        append("Hello")  // this.append("Hello")
        append(" ")
        append("World")
    }

    println(result1)  // "Hello World"
    println(result2)  // "Hello World"
}

// Android场景:DSL风格的View构建
class ViewBuilder {
    fun createLayout(init: LinearLayout.() -> Unit): LinearLayout {
        val layout = LinearLayout(context)
        layout.init()
        return layout
    }
}

// 使用示例
fun buildUI() {
    val layout = ViewBuilder().createLayout {
        orientation = LinearLayout.VERTICAL
        addView(TextView(context).apply {
            text = "标题"
            textSize = 18f
        })
        addView(Button(context).apply {
            text = "提交"
        })
    }
}

private val context: Context get() = TODO()

示例5:Lambda捕获变量

// Lambda可以访问外部变量(闭包)
fun testClosure() {
    var sum = 0
    val numbers = listOf(1, 2, 3, 4, 5)

    // Lambda捕获并修改外部变量
    numbers.forEach {
        sum += it
    }

    println("Total: $sum")  // 15
}

// Android场景:计数器
class CounterActivity : AppCompatActivity() {
    private var clickCount = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val button = findViewById<Button>(R.id.btnClick)
        val textView = findViewById<TextView>(R.id.tvCount)

        // Lambda捕获并修改Activity成员变量
        button.setOnClickListener {
            clickCount++
            textView.text = "点击次数: $clickCount"
        }
    }
}

// 注意:Lambda捕获可能导致内存泄漏
class LeakExample {
    fun setupCallback() {
        val heavyObject = HeavyObject()

        // ❌ 危险:Lambda持有heavyObject引用
        GlobalScope.launch {
            delay(10000)
            heavyObject.doSomething()  // 10秒内heavyObject无法被回收
        }

        // ✅ 改进:使用弱引用
        val weakRef = WeakReference(heavyObject)
        GlobalScope.launch {
            delay(10000)
            weakRef.get()?.doSomething()
        }
    }
}

class HeavyObject {
    fun doSomething() {}
}

⚡ 关键要点

1. Lambda语法糖总结

// 完整形式
list.filter({ item -> item > 0 })

// 尾随Lambda
list.filter() { item -> item > 0 }

// 省略空括号
list.filter { item -> item > 0 }

// 使用it
list.filter { it > 0 }

2. 何时使用it

// ✅ 推荐:简单表达式
list.filter { it > 0 }
list.map { it * 2 }

// ❌ 不推荐:复杂逻辑,使用命名参数
list.filter { user ->
    user.age > 18 &&
    user.isActive &&
    user.roles.contains("admin")
}

3. Lambda性能考虑

// Lambda会创建对象,有性能开销
// 对于性能敏感的代码,考虑使用inline函数
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
    val result = mutableListOf<T>()
    for (item in this) {
        if (predicate(item)) result.add(item)
    }
    return result
}

4. Lambda vs 匿名函数

// Lambda:不能指定返回类型
val lambda = { x: Int -> x * 2 }

// 匿名函数:可以指定返回类型
val anonymousFunc = fun(x: Int): Int {
    return x * 2
}

// 匿名函数中的return行为更直观
list.forEach(fun(value) {
    if (value == 0) return  // 只退出当前函数
    println(value)
})

🔗 相关知识点

  • #121 高阶函数 - Lambda的使用场景
  • #124 inline内联函数 - 优化Lambda性能
  • #114 apply作用域函数 - 带接收者的Lambda应用
  • #132 let、run、apply组合使用 - Lambda的实战技巧

总结

Lambda表达式是Kotlin最常用的特性之一,极大简化了Android开发中的回调、监听器和函数式操作。掌握Lambda的各种语法糖和最佳实践,能写出更简洁、更易读的代码。注意避免闭包导致的内存泄漏问题。

更多推荐