Kotlin 五大作用域函数详解|let/run/apply/also/with 选型指南+实战避坑

Kotlin 五大作用域函数详解|let/run/apply/also/with 选型指南+实战避坑

📖 前言

作用域函数是 Kotlin 最高频、最核心、最容易混淆的语法特性,也是 Android 开发、Compose 开发、面试必考知识点。

Kotlin 提供了 5 个顶级作用域函数:let、run、apply、also、with 。它们的核心作用一致:临时开启一个作用域,简化对象调用、消除冗余代码、优雅处理空安全、实现链式编程

但绝大多数开发者只会无脑用 letapply,不会精准选型、分不清返回值、不懂嵌套规则,导致代码不规范、埋下隐性 Bug。

本文基于 Kotlin 官方规范 ,从零拆解五大函数的底层差异、适用场景、实战写法、避坑要点、面试考点,读完彻底告别死记硬背,开发时一眼选型。


📊 一、核心总览对照表

五大作用域函数的唯一区别只有两个维度:内部引用方式 + 返回值

函数 内部对象引用 返回值 是否扩展函数 核心用途(一句话总结)
let it Lambda 结果 空安全处理、对象转换、值计算
run this Lambda 结果 对象内部批量逻辑 + 结果返回
apply this 对象自身 对象初始化、属性配置(Builder 模式)
also it 对象自身 附加日志、埋点、无侵入副作用
with this Lambda 结果 已有对象批量操作,精简代码

💡 终极选型口诀

  • 要空安全、要返回新值 → let

  • 要配置对象、链式初始化 → apply

  • 要打印日志、附加操作、不改动对象 → also

  • 对象内批量计算、返回结果 → run

  • 已有非空对象,批量调用方法 → with


二、逐函数深度详解(实战+源码逻辑)

2.1 let:空安全专属、值转换神器

核心特征
  • 内部通过 it 引用对象,语义清晰,支持重命名

  • 返回 Lambda 最后一行结果

  • 扩展函数,天然适配可空对象 ?.

最佳场景
  1. 可空对象非空校验与安全执行(项目最高频)

  2. 对象属性转换、计算出新结果

  3. 避免重复调用对象、简化代码

实战代码
kotlin 复制代码
data class User(val id: Int, val name: String)

fun main() {
    val user: User? = User(1001, "Kotlin博主")

    // 1. 空安全执行:仅非空时执行
    user?.let { safeUser ->
        println("用户ID:${safeUser.id}")
        println("用户名:${safeUser.name}")
    }

    // 2. 值转换:返回新结果
    val userNameLength = user?.let {
        it.name.length
    } ?: 0

    println("名字长度:$userNameLength")
}
优势与避坑
  • ✅ 支持变量重命名,多层嵌套不混淆

  • ✅ 完美适配空安全,杜绝 NPE

  • ❌ 不要用于对象属性赋值(优先 apply)


2.2 apply:对象初始化配置之王

核心特征
  • 内部通过 this 引用对象,可省略 this

  • 返回 对象自身,支持链式调用

  • 专为 对象创建+属性批量赋值 设计

最佳场景
  1. 实体类、控件、Builder 对象初始化

  2. 链式构建对象,替代 Java 冗余 set 方法

  3. 批量赋值、配置参数

实战代码
kotlin 复制代码
data class Student(var name: String = "", var age: Int = 0, var score: Int = 0)

fun main() {
    // 一行完成对象创建+批量赋值,极简优雅
    val student = Student().apply {
        name = "小明"
        age = 18
        score = 95
    }

    println(student)
}
优势与避坑
  • ✅ 代码极简,Android 控件初始化、弹窗配置首选

  • ✅ 返回自身,可无限链式调用

  • ❌ 不会返回计算结果,需要结果值请用 run/let


2.3 also:无侵入副作用操作

核心特征
  • 内部 it 引用对象

  • 返回 对象自身

  • 逻辑与 apply 对称,定位为「附加操作」

最佳场景
  1. 对象创建后打印日志、埋点统计

  2. 链式调用中插入中间操作,不改动主逻辑

  3. 数据校验、状态监听等附加副作用

实战代码
kotlin 复制代码
fun main() {
    val list = mutableListOf<Int>().also {
        // 附加初始化日志
        println("集合开始初始化")
    }.apply {
        // 核心赋值逻辑
        add(1)
        add(2)
        add(3)
    }.also {
        // 结束日志打印
        println("集合初始化完成,数据:$it")
    }
}
核心区别:apply vs also
  • apply :专注 修改对象属性

  • also :专注 观察对象、附加操作,不修改数据


2.4 run:内部逻辑计算+结果返回

核心特征
  • 内部 this 引用对象,省略对象名

  • 返回 Lambda 执行结果

  • 兼具「对象操作」+「结果计算」双重能力

最佳场景
  1. 基于对象属性做批量计算、逻辑处理

  2. 初始化对象同时,直接算出结果

  3. 简化多处调用同一对象的冗余代码

实战代码
kotlin 复制代码
data class Order(val price: Int, val count: Int)

fun main() {
    val order = Order(10, 5)

    // 批量计算并返回结果
    val totalPrice = order.run {
        println("单价:$price,数量:$count")
        price * count
    }

    println("总价:$totalPrice")
}

2.5 with:已有对象批量精简操作

核心特征
  • 非扩展函数,传入对象作为参数

  • 内部 this 引用对象

  • 返回 Lambda 结果

最佳场景

针对已经存在、非空的对象,批量调用方法、访问属性,彻底省略对象名。

实战代码
kotlin 复制代码
fun main() {
    val sb = StringBuilder()

    // 已有对象,批量操作,极简代码
    val result = with(sb) {
        append("Kotlin ")
        append("作用域函数 ")
        append("教程")
        toString()
    }

    println(result)
}
with & run 区别(面试高频)
  • run :扩展函数,支持空安全调用 ?.run

  • with :普通函数,不支持空安全,必须确保对象非空


三、高频两两对比(彻底告别混淆)

3.1 let vs run

  • 相同:都返回 Lambda 结果

  • 不同:let 用 it(适合空安全、嵌套清晰);run 用 this(适合内部批量逻辑)

3.2 apply vs also

  • 相同:都返回对象自身,支持链式

  • 不同:apply 修改配置对象;also 观察附加操作

3.3 run vs with

  • 相同:内部 this、返回 Lambda 结果、批量操作对象

  • 不同:run 可空安全;with 仅用于确定非空对象


四、企业级最佳实践规范

  1. 空安全判断一律用 let,禁止手动 if != null 嵌套

  2. 对象初始化、控件配置一律用 apply,替代零散赋值

  3. 日志、埋点、数据观察用 also,不侵入业务逻辑

  4. 对象内部计算、多逻辑聚合用 run

  5. 已存在确定非空对象的批量操作用 with

  6. 禁止滥用 !! 强转,配合 let/run 实现优雅空安全

  7. 链式组合:创建对象.apply{}.also{} 是项目标准写法


五、面试高频问答总结

Q1:五大作用域函数本质区别?

只有两点不同:内部引用方式(it/this)返回值(自身/结果),其余逻辑完全一致。

Q2:apply 和 also 怎么选?

改属性用 apply,打日志/观察用 also。

Q3:run 和 with 区别?

run 是扩展函数,支持空安全;with 是普通函数,必须非空。

Q4:let 为什么适合空安全?

扩展函数 + 可空调用语法 ?.let,仅非空时执行 Lambda,天然杜绝 NPE。


相关推荐
AC赳赳老秦2 小时前
农产品公开数据应用:OpenClaw 抓取农产品价格、产销公开数据,实现农产品行情动态监测
java·c语言·javascript·python·php·deepseek·openclaw
1001101_QIA2 小时前
从 CPU 到 GPU:高速缓存、指令并行与 CUDA 执行原理入门
java·后端·spring
API快乐传递者2 小时前
淘宝海外商品详情接口实战指南:从全球开放平台到跨境铺货的全链路方案
java·前端·数据库
邪修king2 小时前
Re: Linux系统篇(十八):进程篇(七): 进程深度解析:从 fork 创建到退出的完整旅程(附写时拷贝原理 + 代码实战)
java·linux·运维
m0_587383002 小时前
24小时自助健身系统源码实战:从架构设计到部署落地
java·架构·系统架构·需求分析
卓怡学长2 小时前
w156一周穿搭App的设计与实现
java·spring boot·spring·maven·intellij-idea
万年咸鱼2 小时前
Java PrintStream 详解:从基础用法到实战技巧
java·开发语言·python
吴声子夜歌2 小时前
ApacheCommons——commons-compress(解压缩工具)
java·apache