FOUNDRY · 铸造车间 · KOTLIN 2011—2026
坩埚
C R U C I B L E
你写了几年 Kotlin,?. 和 data class 早已成为肌肉记忆,代码 review 时你是那个挑别人毛病的人。但如果有人问你:扩展函数为什么不能被 override?value class 什么时候会偷偷装箱?那个 WhileSubscribed(5000) 的 5000 到底在等什么?—— 你能不能不查文档就答上来?
这本小书不教语法。它把你每天在用的东西扔回坩埚:反编译开看它编译成了什么、拆开 suspend 看那台状态机、把 inline 展开到调用点。然后补上你不在的这几年 —— K2、guard conditions、context parameters、Rich Errors —— 出炉时,你手上的应该不再是「熟练」,而是「说得清」。
炉 I · 装炉 CHARGING
你以为你会 Kotlin
先证明这本书值得读:十道题,和一次反编译。
炉 II · 熔炼 SMELTING
类型系统的锋刃
Kotlin 最锋利的地方在类型系统。六章磨完刃:空安全的边界在哪、编译器什么时候不敢帮你、泛型为什么总是编译不过。
03
空安全的边界:平台类型与 !! 的正当用法
Kotlin 的空安全有一个洞,叫平台类型
String!:它让 NPE 重新变得可能,而且编译器一声不吭。本章画清这条边界,顺便判决 !!、lateinit 与 by lazy 各自的正当使用场景。
▸ 平台类型陷阱台
04
智能转换与 contracts:编译器为什么不敢转
为什么 var 属性判空之后还是不给你智能转换?因为编译器不敢假设它没被别的线程改掉。理解了这条规则,再用 contract 亲手教编译器:让你自己的 isNotNullOrBlank() 也能触发智能转换。
05
泛型与型变:in、out、reified 的真相
生产者 out、消费者 in —— 一条规则解释 List 为什么能协变而 MutableList 不能。再讲 JVM 擦除,以及 Kotlin 是怎么用 reified 从擦除的牢笼里越狱的。
▸ 型变台
06
value class:零开销抽象与装箱的时机
把 String 型的 userId 和 orderId 混着传,是最经典的线上事故。value class 给它们上类型,而且号称零开销 —— 但那个「零」是有边界的:可空、泛型、集合元素,装箱就悄悄回来了。
07
sealed + when:让非法状态无法表示
一个 isLoading + hasError + data? 的 UiState 能表示 32 种状态,而你的业务只承认 4 种 —— 剩下 28 种鬼状态就是你的 bug 来源。用 sealed 把它们从类型里删掉,再用 2.1 的 guard conditions 把嵌套 if 也删掉。
▸ 状态空间计数
08
委托:by 的展开,与 2.4 的显式后备字段
by lazy 只是委托的一个特例。看懂 getValue/setValue 的展开,你就能自己写出 by preference()、by savedState()。最后用 2.4 转正的显式后备字段,删掉 ViewModel 里那对 _state / state 样板。
炉 III · 塑形 FORMING
Kotlin 能做而 Java 做不到的
这一炉全是「Java 抄不走」的东西:内联带来的零开销与非局部返回、带接收者的 lambda、以及 2.4 刚转正的 context parameters。
09
inline:零开销 lambda 与非局部返回
为什么 Kotlin 敢让标准库里全是高阶函数,而 Java 8 的 Stream 至今背着对象分配的包袱?因为
inline 把 lambda 粘贴进了调用点。它还白送两样 Java 永远给不了的东西:非局部返回,和 reified。
▸ inline 展开台
10
扩展与作用域函数:静态分派的真相
扩展函数是静态方法,所以它不听多态的话 —— 这个坑每个 Kotlin 开发都会踩一次。后半章解决那个万年争论:let / run / also / apply / with 到底该用哪个?给你一棵能一路点到底的决策树。
▸ 作用域函数决策树
11
DSL、运算符与解构:把语言掰弯给业务用
带接收者的 lambda 是 Kotlin 的独门武器:Compose、Gradle KTS、Ktor 路由全靠它。本章教你造一个自己的 DSL,用 @DslMarker 堵住作用域泄漏,顺带讲 2.3.20 的名字解构 —— 因为位置解构写反顺序,编译器是不会吭声的。
▸ DSL 拼装台
12
context parameters:2.4 转正的隐式依赖
为了最底层那一行日志,你把 logger 顺着五层函数签名往下传 —— 这叫参数穿透。context parameters 让依赖成为「作用域里的空气」,而且全程类型安全、编译期可查。它也是 Arrow Raise DSL 能成立的地基。
13
不可变:copy 的陷阱,与 Compose 的帧率
data class 的 copy() 是浅拷贝,嵌套一层就开始咬人。而在 Compose 里,不可变不是洁癖而是性能:参数「稳定」的 Composable 才能跳过重组 —— 一个 List 字段就足以让你的列表每帧重组。
炉 IV · 浇铸 CASTING
协程深水区
你天天写 viewModelScope.launch,但可能从没看过它编译成什么。六章:从状态机讲到取消传播,从 async 的异常时机讲到那个神秘的 5000。
14
suspend 的真身:CPS 变换与状态机
suspend 关键字买到的到底是什么?一台状态机:编译器给函数偷偷加一个 Continuation 参数,把局部变量存进堆里的对象,然后 return —— 线程被彻底交还。看懂它,「为什么能开十万个协程」就不再是玄学。
▸ 状态机透视
15
结构化并发:Job 树与协作式取消
一个子协程失败,为什么把兄弟们全拖死了?取消为什么对 Thread.sleep 无效?withContext(NonCancellable) 什么时候才该出场?把 Job 树画出来,这些问题一次答完。
▸ 取消传播树
16
调度器:线程、withContext 与 Android 主线程
Dispatchers.IO 的 64 个线程从哪来?为什么 suspend 函数应该「自己管好自己的线程」,而不是让调用方去包 withContext?以及 Main.immediate 省下的那一次 post,为什么在 Compose 里能看见。
17
协程异常:async 的异常为什么迟到
协程里最反直觉的一课:try/catch 包住 async 抓不到异常,因为它要等到 await() 才抛。launch 和 async 的异常走两条完全不同的路,而 CoroutineExceptionHandler 装错地方就是个摆设。
18
Flow:冷流、背压与操作符融合
Flow 的背压是白送的 —— 因为 emit 是 suspend 的,收得慢就发得慢。而 buffer / conflate / collectLatest 这三个操作符,行为差别大到能决定你的 UI 是流畅还是卡顿。弹珠台上放一遍就全懂了。
▸ 弹珠台
19
StateFlow 与 Android:那个 5000 到底是什么
你抄了无数遍的 stateIn(scope, WhileSubscribed(5000), Loading),那个 5000 是在等屏幕旋转。本章讲透 StateFlow 与 SharedFlow 的分工、为什么必须用 collectAsStateWithLifecycle,以及怎么用 Turbine 把 Flow 测干净。
炉 V · 淬火 QUENCHING
错误、函数式与性能
三章硬货:把错误做成值(一路做到 2.4 的 Rich Errors)、把 Arrow 用在刀刃上、把性能账算清楚。
20
错误建模:从异常到 2.4 的 Rich Errors
Kotlin 取消了受检异常,于是「这个函数会怎么失败」成了一句没人回答的问题。三种活法当场对照:异常、sealed + Result、以及 2.4 用联合类型写进签名的
User | NotFound | Offline。顺便说清 runCatching 为什么会吞掉协程取消。
▸ 错误铁路
21
Arrow:Raise DSL、错误累积与光学器件
Arrow 不是「把 Kotlin 写成 Haskell」,它解决三个具体问题:错误类型不该被焊死成 Throwable、表单校验要一次报全所有错、深层不可变对象的更新不该写成五层 copy。以及一份诚实的清单:什么时候不该引入它。
22
性能:装箱、Sequence、协程与 R8
Kotlin 的抽象大多是零开销的,但「大多」不是「全部」:Int? 会装箱、List<Int> 全是对象、每个捕获 lambda 都在分配。本章把账一笔笔算清,并教你什么时候不该优化。
▸ Sequence 计数器
炉 VI · 回火 TEMPERING
你不在的这几年
如果你有一阵子没跟 Kotlin 了,这一炉是补课:语言、工具链、疆域,一次性交代清楚。每个特性都标了版本和稳定状态。
23
巡礼 2.0 → 2.4:K2 之后发生的一切
K2 编译器、when 的守卫条件、非局部 break、未用返回值检查器、名字解构、context parameters 转正、显式后备字段、Rich Errors、集合字面量、标准库里的 Uuid 与 Clock —— 一条时间线,每项都给「旧写法 / 新写法」对照。
▸ 特性时间线
24
工具链:KSP2、编译器插件与 Amper
kapt 已经退休了(它跑起来要先把 Kotlin 假装成 Java)。KSP2 是继任者。再往上看:编译器插件是怎么把
data class 和 Compose 的重组塞进你的字节码的,以及 JetBrains 正在推的 Amper 想终结什么。
25
疆域:KMP、Swift Export 与用 Kotlin 写 Agent
你的 Android 技能现在能覆盖多大地盘:KMP 共享业务逻辑(不是共享 UI)、Compose Multiplatform、Swift Export 让 iOS 侧直接 await 你的 suspend 函数,以及 Koog —— 用 Kotlin 写 AI Agent。给一份务实的判断:哪些该跟,哪些该等。
26
出炉:一份进阶自检表,和什么时候仍该写 Java
回到第 1 章那十道题。附一份可以贴在 PR 模板里的 Kotlin 代码审查清单、一条继续深造的路线,以及一个不讨喜但诚实的答案:有些时候,你确实还该写 Java。