Kotlin 匿名函数与 Lambda 实战心法

Kotlin 匿名函数与 Lambda 实战心法

目标:不是“知道概念”,而是站在写业务代码和做架构设计的角度,理解:
这些语法解决什么实际问题、在什么场景下用、用好之后代码会怎样变“短、稳、好维护”。


一、什么是匿名函数:为什么需要“没有名字的函数”?

  • 概念(通俗版)
    没有名字的函数,就是“匿名函数”。
    平时我们写:

    • 有名字:fun add(a: Int, b: Int): Int
    • 匿名:fun(a: Int, b: Int): Int { ... }(没有函数名)
  • 为什么要学 / 有什么用?

    • 当这段逻辑只在一个地方临时用一下时,没必要为它起名、到处找定义。
    • 结合集合操作(map / filter)或回调(网络请求回调、点击事件)时,匿名函数可以就近编写逻辑,阅读成本更低。
  • 示例:把逻辑就地写清楚

// 有名字的函数
fun add(a: Int, b: Int): Int {
    return a + b
}

// 匿名函数,存到变量里
val addAnon = fun(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    println(addAnon(1, 2)) // 3
}

二、Lambda:实际项目里最常见的“函数写法”

  • 概念(通俗版)
    Lambda 是一种更简短的“匿名函数”写法,可以当成“函数的字面量”(像 123"abc" 那样的字面量)。

  • 为什么要学 / 有什么用?

    • Kotlin 生态(特别是集合 API、协程、Flow、Compose、Android 回调)大量使用 Lambda
    • 不会 Lambda,几乎看不懂现代 Kotlin 代码;会了 Lambda,可以写出声明式、可组合的代码(例如:一眼看出“先过滤、再排序、再映射”)。
  • 基本形式

// 函数类型:(参数列表) -> 返回类型
// Lambda:{ 参数列表 -> 函数体 }

val addLambda: (Int, Int) -> Int = { a, b ->
    a + b
}

val sayHi: () -> Unit = {
    println("Hi")
}

三、函数类型 与 隐式返回:为“可组合能力”打地基

1. 函数类型(Function Type)

  • 概念(通俗版)
    在 Kotlin 里,函数本身也可以作为一种“类型”,像 IntString 一样:

    • (Int) -> String:接收一个 Int,返回一个 String 的函数
    • () -> Unit:不接收参数、不返回值的函数(Unit 类似 Java 里的 void
  • 为什么要学 / 有什么用?

    • 一旦你能“把函数当成数据传来传去”,就可以搭建出强可组合的业务框架
      • 统一的重试策略、埋点包装、鉴权包装、统一异常处理等,都可以写成“包函数的函数”。
    • 这也是理解协程、Flow、Compose、路由中间件等高级用法的基础。
  • 示例

// f 的类型是:接收两个 Int,返回 Int 的函数
val f: (Int, Int) -> Int = { x, y -> x + y }

fun main() {
    println(f(3, 4)) // 7
}

2. Lambda 的隐式返回

  • 隐式返回(通俗版)
    对于 Lambda,最后一行表达式 的值,就是返回值,一般不写 return

  • 实战价值

    • 单行 Lambda 用隐式返回,可以让集合链式调用更“像一条业务描述”,减少噪音:
      • orders.filter { it.paid }.sumOf { it.amount }
    • 但在复杂 Lambda 中,适当拆成多个语句、加临时变量,会更利于调试。
// 匿名函数写法(显式 return)
val add1 = fun(a: Int, b: Int): Int {
    return a + b
}

// Lambda 写法(隐式返回)
val add2: (Int, Int) -> Int = { a, b ->
    a + b      // 这行的结果就是返回值
}

四、函数参数 与 it:在“可读性”和“简洁”间做取舍

1. 普通参数写法

val square: (Int) -> Int = { x ->
    x * x
}

2. it 关键字(单参数简写)

  • 概念(通俗版)
    当 Lambda 只有一个参数时,可以省略参数名和 ->,Kotlin 会自动给你一个名字:it
    it 就代表“传进来的那个唯一参数”。

  • 实战建议

    • 简单场景(1–2 个字段计算)用 it,很紧凑:
      • users.map { it.name }
    • 复杂场景建议自己起名,避免“一屏幕全是 it”看不懂:
      • orders.filter { order -> order.status == PAID && order.amount > 100 }
// 完整写法
val square1: (Int) -> Int = { x -> x * x }

// 使用 it 简写
val square2: (Int) -> Int = { it * it }

val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 }  // 等价于 list.map { x -> x * 2 }

五、匿名函数的类型推断:既省字数,又不牺牲可读性

  • 类型推断(通俗版)
    Kotlin 会根据上下文自动推断 Lambda 的参数类型和返回类型,你不必到处写类型。
val nums = listOf(1, 2, 3)

// map 期望的是 (Int) -> Int
val result = nums.map { it * 10 }
// 等价于:nums.map { x: Int -> x * 10 }
  • 一般规律与实战取舍:
    • 能准确推断时省略类型,让表达式更紧凑;
    • 边界接口/公共 API 中要保留类型(特别是公开函数签名),提高自说明性;
    • 调试复杂泛型时,暂时把类型写全,有利于理解编译器在做什么。

六、定义“参数是函数”的函数(高阶函数):抽取可复用的“流程模板”

  • 高阶函数(通俗版)
    接收函数作为参数,或返回一个函数的函数,叫“高阶函数”。
    可以理解为:函数可以把函数当参数/当返回值来用

  • 为什么要学 / 有什么用?

    • 把“流程”抽出来,把“细节”交给 Lambda,让业务代码像拼乐高一样可组合
      • 网络请求统一做:显示 Loading → 请求 → 兜底错误提示;
      • 数据库操作统一做:开启事务 → 执行具体逻辑 → 提交/回滚。

1. 参数是函数的函数:把“变化点”外包出去

// operation 是一个函数:(Int, Int) -> Int
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

fun main() {
    // 传入 Lambda
    val sum = calculate(3, 5) { x, y -> x + y }
    val mul = calculate(3, 5) { x, y -> x * y }

    println(sum) // 8
    println(mul) // 15
}

2. Lambda 作为最后一个参数的简略写法:DSL 味道的基础

  • 规则(通俗版)
    当 Lambda 是函数的最后一个参数时,可以把 { ... } 拿到括号外。
    如果函数只有一个 Lambda 参数,甚至可以省略圆括号。

  • 为什么重要?

    • 这是 Kotlin 各种“小 DSL”(领域特定语言)的基础,比如:
      • apply { }run { }also { } 等标准库函数;
      • Room 配置、Gradle Kotlin DSL、Ktor 路由、Jetpack Compose UI DSL。
// 原始写法
calculate(3, 5, { x, y -> x + y })

// 最后一个参数是 Lambda,可写成:
calculate(3, 5) { x, y -> x + y }

fun runTask(task: () -> Unit) {
    task()
}

// 只有一个 Lambda 参数时:
runTask {
    println("running...")
}

七、函数内联(inline function):写“语法糖”的性能安全网

  • 概念(通俗版)
    inline 是编译器优化关键字,意思是:“把函数的代码拷贝到调用处”,而不是在运行时真正去调用。
    这样可以减少高阶函数和 Lambda 带来的额外开销(如对象创建、函数调用栈)。

  • 为什么要学 / 有什么用?

    • 当你大量使用高阶函数来封装“包装逻辑”(埋点、日志、锁、事务)时,频繁的 Lambda 分配可能带来性能问题;
    • inline 让你可以放心大胆写优雅的高阶函数封装,而不用太担心小 Lambda 的开销。
inline fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("cost: ${end - start} ms")
}

fun main() {
    measureTime {
        Thread.sleep(100)
    }
}
  • 记忆:
    • inline 更像是“给语法糖兜底的优化开关”,对 API 设计层面非常重要。

八、函数引用(Function Reference):把已有函数无缝接入新框架

  • 概念(通俗版)
    :: 可以拿到“函数本身”,而不是调用结果。
    就像“把函数当成一个值传着玩”。

  • 为什么要学 / 有什么用?

    • 当你已经有一堆老函数,但现在要接入一个需要 (T) -> R 的新 API 时,函数引用可以免改老函数、直接接进去
    • 在路由表、事件总线、校验器注册等场景,函数引用可以让映射表更直观。
fun isEven(x: Int): Boolean = x % 2 == 0

fun main() {
    val nums = listOf(1, 2, 3, 4)

    // 直接传函数引用
    val evens = nums.filter(::isEven)

    println(evens) // [2, 4]
}
  • 对比 Lambda 写法:
nums.filter { x -> isEven(x) }
nums.filter(::isEven)    // 更简洁

九、函数类型作为返回类型:构建“可配置的行为”

  • 概念(通俗版)
    函数不仅可以接收函数参数,还可以返回一个函数,常用于策略、工厂等模式。

  • 价值点

    • 可以根据配置、环境(测试 / 线上)、权限等,动态生成不同策略函数
    • 复用一整套“行为组合”,常见于:价格策略、限流策略、重试策略等。
// 返回一个 (Int) -> Int 的函数
fun createAdder(n: Int): (Int) -> Int {
    return { x -> x + n }
}

fun main() {
    val add5 = createAdder(5)   // 得到“加 5 的函数”
    println(add5(10))           // 15

    val add10 = createAdder(10)
    println(add10(10))          // 20
}

这里的 Lambda 使用了外部的 n,就是“闭包”。


十、闭包(Closure):让函数“带着状态”跑

  • 概念(通俗版)
    闭包 = 函数 + 它记住的外部变量。
    Lambda / 匿名函数可以“带着”它创建时环境里的变量一起存活和工作。

  • 为什么要学 / 有什么用?

    • 你可以不引入复杂的类,直接用闭包构造轻量级带状态的逻辑单元
    • 与协程、Flow 配合时,闭包是“把外部上下文带入异步环境”的关键。
fun makeCounter(): () -> Int {
    var count = 0  // 外部变量,被 Lambda 捕获

    return {
        count++    // 使用并修改外部变量
        count
    }
}

fun main() {
    val counter1 = makeCounter()
    println(counter1()) // 1
    println(counter1()) // 2
    println(counter1()) // 3

    val counter2 = makeCounter()
    println(counter2()) // 1  // 新的闭包,有自己的 count
}
  • 通俗理解:
  • 每次调用 makeCounter(),就生成了一个“自带私有存储空间的函数对象”,这个私有空间里的变量不会被外部直接访问,但会一直存在。

十一、Lambda 与匿名内部类:现代 Kotlin 写法的“代沟”

在 Android 等场景最常见,比如 setOnClickListener

1. 匿名内部类(Anonymous Inner Class)

  • 概念(通俗版)
    临时创建一个没有名字的类,只用一次,多用于实现接口或抽象类。
button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        println("clicked")
    }
})

2. Lambda 写法(SAM 转换)

  • SAM(Single Abstract Method):只有一个抽象方法的接口。
    Kotlin 允许对这种接口使用 Lambda 的简化写法。
// 接口只有一个抽象方法时:
button.setOnClickListener { view ->
    println("clicked: $view")
}

// 单参数时也可用 it
button.setOnClickListener {
    println("clicked: $it")
}

3. 本质差异(简化理解)

  • 匿名内部类
    • 本质是新建了一个没有名字的类 + 一个实例。
    • this 指向的是那个匿名类的实例。
  • Lambda
    • 更偏“函数字面量”,编译器会做优化,比如用于 SAM 接口时减少额外类。
    • Lambda 里 this 一般指向外部类实例。

4. 实战建议

  • 能用 Lambda 的地方优先用 Lambda,代码更短、更清晰;
  • 旧代码中大量匿名内部类,一般可以安全地重构为 Lambda(单方法接口);
  • 接口有多个抽象方法时,才必须用匿名内部类。

  • 十二、从“语法”到“工程价值”的迁移

  • 匿名函数 / Lambda:让逻辑就近、短小,降低“跳转查定义”的成本;

  • 函数类型 + 高阶函数:支撑一切“可组合”的业务框架和 DSL 设计;

  • 隐式返回 + it + 类型推断:让链式调用像“业务语句”,读起来是中文,而不是类型噪音;

  • 内联函数:给你写“优雅封装”提供性能保险;

  • 函数引用 + 函数类型返回 + 闭包:让行为可以被工厂化、配置化、带状态地复用;

  • Lambda vs 匿名内部类:区分“老式写法”和“现代 Kotlin”,是代码风格和团队工程成熟度的标志之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值