炉 VI · 回火CH 26进度 26/26

出炉:一份进阶自检表,和什么时候仍该写 Java

二十五章之前,我用十道题把一条河指给你看:「会用」和「说得清」之间的那条河。现在你应该已经在对岸了。这一章做三件事:把那十道题的答案系统地收口(每一道现在都该有一个「因为…所以…」)、交给你一份能贴进 PR 模板的审查清单、然后给一个不讨喜但诚实的答案 —— 有些时候,你确实还该写 Java。

自检审查清单什么时候写 Java继续深造

回到那十道题

现在再看一遍。如果你能对每一道说出「因为…所以…」,这本书的任务就完成了

一句话答案底下的原理
Q1 data class 的 equals只认主构造里的属性它是编译期生成的代码(02
Q2 var 属性不智能转换编译器无法证明两次读取值相同智能转换只对「能证明是死存储」的东西生效(04
Q3 扩展函数不听多态它是静态方法,按静态类型分派接收者只是第一个参数(10
Q4 companion 不是 static它是嵌套类的单例实例同上:糖有价签02
Q5 value class 何时装箱可空、泛型、集合、当 Any 用时零开销的前提是「类型能被抹掉」(06
Q6 List 能协变,MutableList 不能只生产 → out;又读又写 → 不变危险来自写入,不来自读取(05
Q7 try/catch 抓不到 async异常存进 Deferred 等 await;同时沿 Job 树炸掉父作用域异常走树,不走栈17
Q8 conflate vs collectLatest一个「不启动」,一个「启动了再杀掉」背压来自 emit 是 suspend 的(18
Q9 那个 5000在等屏幕旋转(配置变更的重订阅宽限期)热流的启动策略(19
Q10 runCatching 的危险吞掉 CancellationException,协程取消不掉取消就是靠这个异常传播的(15

注意这张表右边那一列 —— 十个问题,只有五六条原理。这就是「说得清」的本质:不是记住一百条规则,而是掌握少数几条能推导出规则的原理

◆ 锭 · 全书压缩成六句话
  1. 糖有价签。Kotlin 的每个便利特性都编译成了具体的东西,而那个东西决定了它的行为边界(02)。
  2. 让编译器替你干活。能在编译期发现的错误,就不要留到运行时 —— sealed 的穷尽检查、value class 的类型、context parameters 的能力约束,全是这一条(06、07、12)。
  3. 把状态空间压到最小。非法状态不该「被防御」,应该无法表示(07)。
  4. 协程的异常和取消走 Job 树,不走调用栈。你的 try/catch 直觉在这里失效(15、17)。
  5. 不可变让系统敢于缓存。Compose 跳过重组、StateFlow 去重、状态回放,全靠它(13、19)。
  6. 失败是值,不是控制流。从 sealed 到 Rich Errors,Kotlin 十年演进的主线之一(20、21)。

一份可以贴进 PR 模板的审查清单

这是本书最实用的产出。直接抄进你团队的 PR 模板或者 review checklist

✚ Kotlin 代码审查清单

类型与建模

  • 状态类是 sealed 吗?还是一堆 boolean + 可空字段?(07)
  • when 里有 else 吗?如果主语是 sealed/enum,删掉它(07)
  • 签名里有相邻的同类型参数(两个 String、两个 Long)吗?上 value class(06)
  • data class 的字段有 MutableList 吗?(13)
  • 从 Java / JSON / Intent 来的值,类型钉死了吗?(03)

空安全

  • 每个 !! 旁边有解释「为什么不可能是 null」的注释吗?(03)
  • 每个 lateinit:会不会有东西把它抽走(视图销毁)?(03)

协程(重灾区)

  • runCatching / catch (e: Exception) 包住 suspend 调用了吗?→ 正在吞取消信号(15、20)
  • asynccoroutineScope 里吗?被 await 了吗?(17)
  • GlobalScope 吗?(15)
  • CPU 密集循环里有 ensureActive() 吗?(15)
  • withContext(Dispatchers.IO) 包的是 Retrofit/Room 吗?→ 多余(16)
  • CPU 密集的活扔进 IO 池了吗?→ 该用 Default(16)
  • finally 里的 suspend 清理有 NonCancellable 吗?(15)

Android / Flow

  • collectAsState() → 换成 collectAsStateWithLifecycle()(19)
  • stateIn 用的是 WhileSubscribed(5000) 吗?(19)
  • _state.value = → 换成 _state.update { }(19)
  • UiState 里有事件性字段(toastMsg、navigateTo)吗?(19)
  • 高频 Flow 有 conflate() 吗?(18)
  • 自定义 View 的构造函数有 @JvmOverloads 吗?(02)

什么时候仍该写 Java

说好的诚实答案。这四种情况,Java 依然是更好的选择

场景为什么
一个纯 Java 团队的存量模块,改动很少混编的成本(工具链、心智、review)大于收益。「全面 Kotlin 化」不是目标,交付才是。让它安静地活着
要给 Java 使用者用的公开 API / SDK第 2、6 章讲过:默认参数无效、companion 难调、value class 的方法名被混淆、suspend 函数难用。你的 Kotlin 糖会变成使用者的苦。(也可以用 Kotlin 写但保持「脱糖后依然好看」—— 那需要功力)
大量依赖 Java 注解处理器的老框架某些老框架对 Kotlin 的 final 默认、null 检查、property 语义有敌意。为它们打补丁的时间,可能比重写还多
团队里没有一个人真的懂 Kotlin这是最诚实的一条。用一门没人懂的语言写出来的代码,比用一门大家都懂的语言写出来的笨代码更危险 —— 因为没人能 review 它,没人敢改它

还有一条元规则,比上面四条都重要:别把语言当宗教。本书花了二十五章讲 Kotlin 的优势,但它们全都是工程手段,不是信仰。能让你的 App 更稳、更快交付、更好维护的东西才是对的 —— 有时候那个东西是 sealed + Raise,有时候它就是一个朴素的 if (x == null) return

继续深造:一条务实的路线

  • 先把这本书的「动手锻打」做完。说真的 —— 读一百章不如把一个 UiState 重构成 sealed 并数出「32 → 4」这个数字。进阶不发生在阅读时。
  • 读标准库的源码。Iterables.kt(集合操作怎么实现的)和 Standard.kt(那五个作用域函数,加起来不到五十行)开始。你会发现你以为的「语言特性」大多只是普通函数 —— 这个认知比任何教程都解放人。
  • 读 kotlinx.coroutines 的源码。Builders.common.ktlaunch/async)到 JobSupport.kt(那棵树)。硬,但值 —— 第 14—17 章的一切都在里面。
  • 关注 KEEP 仓库(Kotlin Evolution and Enhancement Process)。语言的下一步在那里讨论,而且讨论过程本身就是最好的语言设计教材。
  • 去上游看看。Kotlin 的很多好东西 —— sealed(和类型)、Optional、模式匹配、不可变、Either —— 都不是它发明的。想知道它们的原产地长什么样,本站有一本姊妹作 《上游》,它的第 18 章正好是从那边走回 Kotlin/Java 的落地清单,和这本书能接上。

最后一句

这本书从头到尾只在做一件事:把「熟练」换成「说得清」。

这两者的差距,在代码能跑的时候看不出来。它出现在你 review 别人代码时能不能一眼看出那个 runCatching 会吞掉取消信号;出现在新人问「为什么这里不能智能转换」时你是回答「Kotlin 就这样」还是「因为编译器无法证明」;出现在 AI 给你一段能跑但有隐患的代码时,你能不能看出来

写代码的成本正在归零,判断的成本没有。—— 这就是你花时间读完这二十六章的全部理由。

出炉了。回到你的项目里去,找那个 isLoading + hasError 的 UiState。