FOUNDRY · 铸造车间 · KOTLIN 2011—2026

C R U C I B L E

你写了几年 Kotlin,?.data class 早已成为肌肉记忆,代码 review 时你是那个挑别人毛病的人。但如果有人问你:扩展函数为什么不能被 overridevalue class 什么时候会偷偷装箱那个 WhileSubscribed(5000) 的 5000 到底在等什么?—— 你能不能不查文档就答上来?

这本小书不教语法。它把你每天在用的东西扔回坩埚:反编译开看它编译成了什么、拆开 suspend 看那台状态机、把 inline 展开到调用点。然后补上你不在的这几年 —— K2、guard conditions、context parameters、Rich Errors —— 出炉时,你手上的应该不再是「熟练」,而是「说得清」。

26 章 · 6 炉 13 个零依赖互动 Demo 反编译台 / 状态机透视 / Flow 弹珠台 Kotlin 2.4 · 2026
🔥 装炉:先做十道题
炉 I · 装炉 CHARGING

你以为你会 Kotlin

先证明这本书值得读:十道题,和一次反编译。

炉 II · 熔炼 SMELTING

类型系统的锋刃

Kotlin 最锋利的地方在类型系统。六章磨完刃:空安全的边界在哪、编译器什么时候不敢帮你、泛型为什么总是编译不过。

03 空安全的边界:平台类型与 !! 的正当用法 Kotlin 的空安全有一个洞,叫平台类型 String!:它让 NPE 重新变得可能,而且编译器一声不吭。本章画清这条边界,顺便判决 !!lateinitby 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 classcopy() 是浅拷贝,嵌套一层就开始咬人。而在 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() 才抛。launchasync 的异常走两条完全不同的路,而 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 用在刀刃上、把性能账算清楚。

炉 VI · 回火 TEMPERING

你不在的这几年

如果你有一阵子没跟 Kotlin 了,这一炉是补课:语言、工具链、疆域,一次性交代清楚。每个特性都标了版本和稳定状态。