【日常知识积累】Kotlin let 函数、inline 函数以及 DSL

凡事有交代 ------ 接到任务时,主动明确任务目标、背景、利益相关方、交付物、时间节点。

件件有着落 ------ 执行任务过程中,在里程碑节点及时同步,做好预期管理。

事事有回音 ------ 任务完成后,有复盘,有汇报,有总结。

这篇文章记录了近期积累的一些零散知识点,临近元旦、春节,这个时期是一年里工作相对轻松的阶段,同时也是复盘总结过去一年的经验、以及做好明年目标规划的关键节点。利用好,别虚度

你真的需要使用 let 函数吗?

在 Kotlin 中,letapplyalsorun 等等函数,是降低代码行数、提升可读性的利器,然而它们并不是万能的,如果使用不当,会带来相反的效果。

let 的一般用法

kotlin 复制代码
// 如果元素非空,则对其进行绘制
element?.let { render(it) }

// 非 let 写法
if (element != null) {
	render(element)
}

可以看到,let 函数将3行代码精简成为1行,精简了 66% 的代码。它适用于以下场景:

  • 单次调用该对象
  • lambda 行数很少
  • 没有后续依赖

与之相反的条件,就是不适用于 let 的场景。

let 不适用的场景

kotlin 复制代码
// 灾难现场1:行数多、有后续依赖
element?.let { e ->
    config?.let { c ->
        adapter?.let { a ->
            renderer?.let { r ->
                r.render(e, c, a)
            }
        }
    }
}

// 灾难现场2:超过1行使用 let 对象
element?.let {
    doSomething(it)
    doAnotherThing(it)
    doMore(it)
}

以上已经是 Kotlin 的反模式了,这样的代码不断向右偏移(rightward drift)难读并且难改,在进行 Code Review 时一定要识别出来。

替代方案 return fast

在函数的开头,检查变量条件,如果不符合立刻 return,可以在需要的位置增加日志打印,以便追溯问题。

kotlin 复制代码
fun updateView() {
    val e = element ?: return
    val c = config ?: return
    val a = adapter ?: return

    render(e, c, a)
}

为什么这是好代码?

  • 没有嵌套
  • 控制流清晰
  • 可调试(每一行都能打断点)
  • 非常符合 "fail fast" 原则

更进一步,如果 e、c、a 这三个对象经常一起判空,可以将它们本质上是一个状态,推荐对它们进行状态聚合,有利于长期使用。

kotlin 复制代码
// 状态聚合
data class RenderState(
    val element: Element,
    val config: Config,
    val adapter: Adapter
)

// 只有一个变量
val state: RenderState? = ...

// 使用时判断该变量
fun updateView() {
    val s = state ?: return
    render(s)
}

此外,如果发现,代码里多处进行判空操作,则可以利用 DSL,将判空操作提取出 withReadyElement 函数,

kotlin 复制代码
// 提取 inline 函数,集中进行可空性处理
private inline fun withReadyElement(block: (Element) -> Unit) {
    val e = element ?: return
    block(e)
}

// 调用点更加美观干净
fun updateView() {
    withReadyElement {
        render(it)
    }
}

inline fun 用于消除 Lambda 对象额外开销

inline fun 是"为 Lambda 买单的工具",用于消除 Lambda 参数引起的对象创建&间接调用开销,同样地,它也不是"银弹",如果使用不当,反而会引起性能下降。

普通 Lambda 函数与 inline Lambda 函数

普通 Lambda 函数

kotlin 复制代码
fun doSomething(block: () -> Unit) {
    block()
}

系统在编译这段代码时,实际上发生了:

  • 创建一个 Function0 对象(表示0个参数、即无参函数)
  • 捕获外部变量,生成闭包
  • 通过 invoke() 间接调用

例如,我们调用 doSomething { println("hello") } 后,编译器实际上创建了一个类,并调用其 invoke 函数。

java 复制代码
class DoSomething$1 implements Function0<Unit> {
    @Override
    public Unit invoke() {
        System.out.println("hello")
        return Unit.INSTANCE;
    }
}

inline Lambda 函数

kotlin 复制代码
inline fun doSomething(block: () -> Unit) {
    block()
}

// 编译后直接展开
println("hello")

标准库的 inline 用法

在 Kotlin 标准库里,这些函数都是 inline 实现

  • let
  • apply
  • run
  • with
  • use
  • synchronized

例如:

kotlin 复制代码
inline fun <T> T.apply(block: T.() -> Unit): T

inline 的负面作用

由于 inline 本质上采用了 复制-粘贴 的模式,因此它会在每个调用点复制一份函数代码,这会造成 APK / DEX 膨胀,对 Android 来说,更会引起 方法数增加,甚至突破 64K 限制

inline 的应用场景

  • 参数是 lambda 类型
  • lambda 非常短
  • 该函数 90% 以上是 lambda 调用
  • 应用在循环 / UI / 热路径中
  • inline 收益 > 代码膨胀成本

DSL,把握好"提升可读性"与"滥用"之间的界限

DSL(Domain-Specific Language,领域特定语言),是一种 只为某个特定领域服务 的语言,目标不是"什么都能干",而是把这个领域的表达做到最清晰、最省心、最不容易写错。

如何理解"领域"呢?在我看来,"领域"是指特定的场景、语境、上下文,它具有一定的重复性,因此通过提炼总结 DSL,能够降低重复使用的成本。"领域"可以是技术上的,也可以是业务场景里的。

GPL 与 DSL

与 DSL 相对的概念,是 通用语言(GPL),Java / Kotlin / C++ 都属于此类,它们功能强大且全面,但随之而来的,是写法严谨甚至冗长,无法做到针对特定场景(领域)进行简化。

DSL 则专注于解决一个特定领域的问题,为此进行了特定优化,短、平、快、准。

GPL、DSL 都可以解决问题,重点在于选择使用哪一个。

DSL 的两大类型:外部 DSL

外部 DSL 具备 独立的语法和解析器 ,更像一门新的编程语言,因此其表达能力很强。同时,由于依赖于特定解析器,故定制成本高。

常见的外部 DSL 有:

  • SQL
  • 正则表达式
  • Gradle Groovy/Kotlin DSL(build.gradle)
  • HTML / CSS

DSL 的两大类型:内部 DSL

内部 DSL 在 Kotlin 语言中是非常常见的,例如前文的 letapplyalso 等均属于此类。可以通过 Kotlin 语法特性,使代码"看起来像新的语言"。Kotlin 的标准库中,有很多 API 本身就是 DSL。举两个常见的例子:

例1,Lambda with Receiver

定义 user DSL,代替 set 函数。

kotlin 复制代码
// 使用 Lambda,域内对象为 User
fun user(block: User.() -> Unit) {
    val u = User()
    u.block()
}

// 使用 DSL
user {
	name = "Lei"
}

// 不使用 DSL
user.setName("Lei")

例2, infix 函数

可以提升可读性,例如定义二元运算符 eq,比较 String 对象:

kotlin 复制代码
// 声明 DSL
infix fun String.eq(value: String)

// 使用 DSL
"status" eq "success"

Gradle Kotlin、Jetpack Compose、HTML 都是典型的 DSL,UI结构=代码结构,起到"看代码知UI"的作用。

DSL 不是炫技工具,而是把业务规则提取为代码的手段

值得设计 DSL 不该用DSL
✅是不是反复写相同结构? ✅业务规则是否固定? ✅现在的代码是否"看不出业务语义"? ✅错误是不是经常发生在"用法不对"? ❌一次性逻辑 ❌需求不稳定 ❌团队 Kotlin 水平参差 ❌DSL 设计者不在一线维护
相关推荐
故事不长丨19 小时前
安卓相机开发:Camera、Camera2与CameraX的使用对比及选型指南
android·相机·camera·camerax·camera2·移动设备·相机开发
_李小白19 小时前
【Android 美颜相机】第七天:GLTextureView 解析
android·数码相机
honortech19 小时前
Android studio中配置gradle和对应的AGP版本
android·ide·android studio
廋到被风吹走20 小时前
【数据库】【MySQL】事务隔离深度解析:MVCC 实现与幻读解决机制
android·数据库·mysql
AC赳赳老秦20 小时前
技术文档合著:DeepSeek辅助多人协作文档的风格统一与内容补全
android·大数据·人工智能·微服务·golang·自动化·deepseek
赛恩斯20 小时前
安卓构建工具D8和R8的区别
android
—Qeyser20 小时前
Flutter CustomScrollView 自定义滚动视图 - 完全指南
android·flutter·ios
鸣弦artha20 小时前
Flutter 框架跨平台鸿蒙开发 —— Image Widget 图片处理:圆角、裁剪、阴影
android·flutter·harmonyos
—Qeyser21 小时前
Flutter ListView 列表组件完全指南
android·flutter·ios
独自破碎E21 小时前
包含min函数的栈
android·java·开发语言·leetcode