Kotlin 的价值,不是少写分号
它真正减少的是「本来能通过编译、最后才在用户手机上炸掉」的状态空间。
下面哪一种写法最适合表达「名字为空时直接返回,否则继续拿到非空名字」?
name!!name?.let { ... }套住后面整段函数val safeName = name ?: return- 先把
name赋给可变全局变量
类型系统是在删路径
String 与 String? 是不同类型,意味着调用者必须在编译期处理「没有值」这条路径。安全调用 ?. 会把空传播下去;Elvis 运算符 ?: 给出替代值或提前退出;!! 则是你向编译器签字:「这里不可能为空,错了就崩。」
平台类型是 Java 互操作留下的洞,例如 Java 返回的 String!。它既可当非空也可当可空,编译器替你做不了决定。边界处尽早归一化:校验、转换成可空类型,或包成明确的领域结果,别让平台类型流进核心逻辑。
表达式让分支必须产出值
if 与 when 都是表达式。把 UI 状态写成一个 when 的结果,编译器能检查每个分支是否返回同一种东西;配合 sealed 类型,还能检查是否漏了新状态。这比先声明一个可空变量、在多个分支里修改它更容易证明完整。
val label = when (status) {
MessageStatus.Pending -> "发送中"
MessageStatus.Sent -> "已发送"
is MessageStatus.Failed -> "重试:${status.reason}"
}
data class 自动生成结构相等、copy 与解构,但 copy 是浅拷贝。内部若放可变列表,两个实例仍可能共享同一份列表。不可变状态需要的是不可变使用方式,不是一个注解或关键字。
五个作用域函数只问两件事
别背口诀,问两件事:对象在块里叫 this 还是 it?表达式返回对象本身,还是块的结果?
| 函数 | 对象名 | 返回 | 常见用途 |
|---|---|---|---|
let | it | 块结果 | 变换、处理可空值 |
run | this | 块结果 | 对象上下文里计算 |
with | this | 块结果 | 对已有对象做一组读取 |
apply | this | 对象 | 配置对象 |
also | it | 对象 | 旁路日志或校验 |
嵌套三层 let 通常不是「很 Kotlin」,而是名字丢了。此时用有意义的局部变量和提前返回更清楚。扩展函数也不是给类加方法;它是静态分派的语法糖,不能访问私有成员,具体调用由接收者的编译期类型决定。
观察空值提前退出、结构相等与引用相等。输出应明确区分 == 和 ===:
display=Ada structural=true referential=false missing=<anonymous>
data class User(val id: String, val name: String?)
fun displayName(user: User?): String {
val name = user?.name?.trim()?.takeIf { it.isNotEmpty() }
?: return "<anonymous>"
return name
}
fun main() {
val a = User("1", " Ada ")
val b = a.copy()
println("display=${displayName(a)}")
println("structural=${a == b}")
println("referential=${a === b}")
println("missing=${displayName(null)}")
}粘到 Kotlin Playground。然后把 ?: return 换成 !!,传入 null,让风险从类型分支变成运行时崩溃。
服务端字段可缺失、Room 迁移前数据不完整、Intent 参数来自旧通知——这些边界才是空值的产地。把 DTO 转成领域模型时一次处理,Compose 层就不必满屏 ?.,也不会在不同页面对同一缺失值给出不同解释。
「Kotlin 空安全消灭了 NPE。」!!、平台类型、初始化顺序与 Java 互操作都能带回 NPE。
Kotlin 把大多数空值路径显式化;你的责任是在系统边界收窄它,而不是用 !! 把编译器闭嘴。
答案是第 3 项。Elvis 加提前返回把剩余作用域里的值收窄为非空,控制流最直。let 适合短变换,不适合为了避空把整段业务嵌进去;!! 只是延迟风险。
这一章的一句话
好 Kotlin 不是更短,而是让不可能与不确定在类型里可见。
下一章把这句话推到底:一个消息同时是 isSending=true、isFailed=true、serverId=null 时,到底是什么意思?用类型改写后,这种组合会直接无法编译。