文章目录

Kotlin 空安全:? 与 !! 操作符

发布于 2026-04-03 17:44:00 · 浏览 2 次 · 评论 0 条

Kotlin 空安全:? 与 !! 操作符

Kotlin 的核心设计目标之一是消除空指针异常(NullPointerException),这在 Java 中被称为“十亿美元的错误”。为此,Kotlin 引入了空安全类型系统,通过编译器强制区分“可为空”和“不可为空”的类型。?!! 是两个关键操作符,用于处理可能为 null 的值。正确使用它们,能让你的代码既安全又简洁。


理解可空类型与非空类型

在 Kotlin 中,类型默认是非空的。例如:

val name: String = "Alice"

这里的 name 不能被赋值为 null。如果你尝试写 name = null,编译器会直接报错。

要允许一个变量为 null,必须显式声明为可空类型,方法是在类型后面加 ?

val name: String? = null

现在 name 可以是字符串,也可以是 null


使用 ? 操作符:安全调用

当你有一个可空类型的变量时,不能直接调用它的方法或属性,因为如果它是 null,就会抛出异常。Kotlin 提供了安全调用操作符 ?. 来解决这个问题。

使用 ?. 访问属性或方法时,如果接收者为 null,整个表达式返回 null,而不是抛出异常

例如:

val length: Int? = name?.length
  • 如果 name"Alice"length 就是 5
  • 如果 namenulllength 就是 null

你还可以链式使用安全调用:

val city: String? = user?.address?.city

只要 useruser.addresscity 中任意一个是 null,整个表达式就返回 null,不会崩溃。


使用 ?: 操作符:Elvis 操作符

安全调用返回的是可空类型,但很多时候你需要一个非空值。这时可以配合 Elvis 操作符 ?:,提供一个默认值。

如果左侧表达式不为 null,返回它;否则返回右侧的值

val displayName: String = name ?: "Guest"
  • 如果 name"Alice"displayName 就是 "Alice"
  • 如果 namenulldisplayName 就是 "Guest"

你甚至可以把安全调用和 Elvis 操作符连用:

val len: Int = name?.length ?: 0

这样 len 一定是 Int 类型,不会是 Int?


使用 !! 操作符:强制断言非空

有时候你确定一个可空变量实际上不是 null(比如经过了 if 判断,或者来自外部系统的保证),但编译器不知道。这时可以用 !! 操作符强制将其视为非空

对一个值使用 !! 会返回其非空类型;但如果该值实际为 null,会立即抛出 KotlinNullPointerException

val name: String? = getNameFromAPI() // 假设你知道它不会返回 null
val upperName: String = name!!.uppercase()

这里 name!! 告诉编译器:“我保证 name 不是 null”,于是你可以直接调用 uppercase()

但要极度谨慎:一旦判断错误,程序就会崩溃。因此,除非你有 100% 的把握,否则不要使用 !!


常见场景与最佳实践

场景 1:从 API 获取数据

假设你从网络请求得到一个用户对象,某些字段可能缺失:

data class User(val name: String?, val email: String?)

安全处理方式:

val user: User? = fetchUser()
val greeting = "Hello, ${user?.name ?: "Anonymous"}!"
```

避免写成:

```kotlin
// 危险!如果 user 是 null,会崩溃
val greeting = "Hello, ${user!!.name}!"

场景 2:集合中的可空元素

val numbers: List<Int?> = listOf(1, null, 3)
val sum = numbers.map { it ?: 0 }.sum()

或者过滤掉 null:

val sum = numbers.filterNotNull().sum()

场景 3:平台类型(来自 Java)

当调用 Java 代码时,Kotlin 无法确定返回值是否可空,这类类型称为“平台类型”,显示为 String!。此时你有两个选择:

  1. 主动声明为可空类型,并使用安全调用:

    val result: String? = javaObject.getName()
    val len = result?.length
  2. 如果你确信它非空,可以赋值给非空类型(但内部仍可能崩溃):

    val result: String = javaObject.getName() // 编译通过,但运行时可能 NPE

推荐做法是始终当作可空处理,除非有明确文档保证。


? 与 !! 对比总结

以下表格对比了两种操作符的核心差异:

特性 ?. (安全调用) !! (非空断言)
行为 若接收者为 null,返回 null 若接收者为 null,抛出 KotlinNullPointerException
返回类型 可空类型(如 T? 非空类型(如 T
安全性 安全,不会崩溃 危险,可能导致运行时崩溃
适用场景 处理不确定是否为 null 的值 你 100% 确定值非 null(如已检查过)

如何避免滥用 !!

如果你发现自己频繁使用 !!,说明代码设计可能有问题。以下是替代方案:

  1. 使用 if 判断

    if (name != null) {
        println(name.length) // 在此作用域内,name 自动转为非空
    }
  2. 使用 let 函数

    name?.let { nonNullName ->
        println(nonNullName.length)
    }
  3. 提前返回或抛出自定义异常

    fun processName(name: String?) {
        val validName = name ?: throw IllegalArgumentException("Name must not be null")
        // 后续使用 validName,它是非空的
    }

这些方式比 !! 更清晰、更可控。


记住:Kotlin 的空安全不是障碍,而是保护伞。优先使用 ?. ?: !! 当作最后手段

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文