第 5 章 · Null 安全 - NPE 终结者

把"运行时炸"提前到"编译期报错" - Kotlin 怎么干掉了 NullPointerException。

🛠 五大空安全武器

?. 安全调用(Safe Call)
x?.method():如果 x 是 null,整个表达式返回 null,**不抛异常**。
类比:像"先敲门再进去",没人应答就掉头走。
?: Elvis 操作符(默认值)
x ?: default:如果 x 为 null,用右边的默认值。
类比:像"备用钥匙" - 主钥匙没带就用备用的。
!! 非空断言(自杀式)
x!!.method():强制当成非空,**为 null 时立即抛 NPE**。
类比:像"我不戴安全带" - 真撞了你自己负责。
⚠️ 99% 的场景都不该用!
as? 安全类型转换
obj as? String:转不成返回 null,不抛 ClassCastException。
类比:像"试穿衣服" - 不合身就脱下来,不强行。
?.let{ } 非空时执行块
x?.let { it.foo() }:x 非空时进入块,块内 it 是非空类型。
类比:像"进门后办事" - 门都没敲开就别进。

🧪 NPE 沙盒:选择 name 的值,看不同操作符的行为

选择值和表达式,点 ▶ 执行

🎯 自我测试

下面每个组合,你预测结果是什么?点击"显示答案"对照。

name = null + name?.length
name = null + name!!.length
name = null + name?.length ?: 0
name = "hi" + name!!.length

🔗 嵌套对象链式判空:user?.address?.city ?: "(无)"

调整每一层的状态("有值"/"null"),看 Kotlin 怎么短路。

1️⃣ user

2️⃣ .address

3️⃣ .city

4️⃣ Elvis 默认


      

⚠️ Java 互操作:平台类型的陷阱

Java 不区分可空/非空(除非加注解),Kotlin 推断出的类型是 String!("既能当 String 又能当 String? 用")。这是 Kotlin 唯一不能保证空安全的地方。

📦 假设的 Java 代码

// Java
public class JavaApi {
    public String getName() {
        // 可能返回 null !
        return Math.random() > 0.5 ? "Alice" : null;
    }
}

🚫 vs ✅ Kotlin 端两种写法

🚫 偷懒写法(埋雷)

// Kotlin 推断为 String!
val name = api.getName()

// 编译能过!但 50% 概率运行时炸
println(name.length)
// → java.lang.NullPointerException
//   at MainKt.main()

✅ 防御性写法

// 明确声明类型
val name: String? = api.getName()

// 编译器强制你处理 null
val len = name?.length ?: 0
println("长度 = $len")

// 或用 let
name?.let { println("got: $it") }

💡 终极建议

  • 调 Java API 时,永远显式声明可空类型 val name: String? = ...
  • 给关键 Java 库加 @Nullable/@NotNull 注解,Kotlin 会识别(IntelliJ、JSR-305、JetBrains 等都支持)
  • Spring、Jackson 等主流框架已经标了,可以直接用