Kotlin 可空类型与空安全 #

一、空安全概述 #

Kotlin 的空安全设计是其最重要的特性之一,可以在编译时发现潜在的空指针异常。

1.1 NullPointerException 问题 #

在 Java 中,NullPointerException 是最常见的运行时异常:

java
// Java 代码
String name = null;
int length = name.length();  // 运行时抛出 NullPointerException

1.2 Kotlin 的解决方案 #

Kotlin 区分可空类型和不可空类型,在编译时进行检查:

kotlin
// 不可空类型
val name: String = "Kotlin"
// name = null  // 编译错误

// 可空类型
val nullableName: String? = null  // 正确

二、可空类型 #

2.1 声明可空类型 #

使用 ? 标记可空类型:

kotlin
val name: String? = null
val age: Int? = null
val list: List<String>? = null

2.2 不可空类型 #

默认类型是不可空的:

kotlin
val name: String = "Kotlin"
val age: Int = 25

// 错误:不能赋值为 null
// name = null
// age = null

2.3 类型兼容性 #

kotlin
// 不可空类型可以赋值给可空类型
val a: String = "Hello"
val b: String? = a  // 正确

// 可空类型不能赋值给不可空类型
val c: String? = "Hello"
// val d: String = c  // 编译错误

三、安全调用操作符 #

3.1 安全调用 ?. #

使用 ?. 安全地调用可空对象的方法:

kotlin
val name: String? = null

// 安全调用
val length = name?.length  // 返回 null,不抛出异常

// 链式调用
val upper = name?.uppercase()?.reversed()

3.2 与普通调用对比 #

kotlin
val name: String? = null

// 普通调用(编译错误)
// val length = name.length

// 安全调用
val length = name?.length  // null

// 非空断言(可能抛出异常)
val forcedLength = name!!.length  // NullPointerException

3.3 链式安全调用 #

kotlin
class Person(val name: String?, val address: Address?)
class Address(val city: String?)

val person: Person? = null

// 链式安全调用
val city = person?.address?.city  // null

四、Elvis 操作符 #

4.1 基本用法 #

使用 ?: 提供默认值:

kotlin
val name: String? = null

// 如果 name 为 null,返回 "Unknown"
val displayName = name ?: "Unknown"

// 等价于
val displayName2 = if (name != null) name else "Unknown"

4.2 与安全调用结合 #

kotlin
val name: String? = null

val length = name?.length ?: 0
val upper = name?.uppercase() ?: "DEFAULT"

4.3 抛出异常 #

kotlin
val name: String? = null

// 如果为 null,抛出异常
val displayName = name ?: throw IllegalArgumentException("Name cannot be null")

// 提前返回
fun process(name: String?) {
    val validName = name ?: return
    println(validName)
}

五、非空断言 #

5.1 使用 !! #

!! 将可空类型转换为非空类型,如果为 null 则抛出异常:

kotlin
val name: String? = "Kotlin"
val length = name!!.length  // 6

val nullName: String? = null
// val error = nullName!!.length  // NullPointerException

5.2 谨慎使用 #

!! 应该尽量避免使用,只在确定不为 null 时使用:

kotlin
// 不推荐
val name: String? = getName()
val length = name!!.length  // 危险

// 推荐
val name: String? = getName()
val length = name?.length ?: 0  // 安全

六、安全类型转换 #

6.1 安全转换 as? #

kotlin
val obj: Any = "Hello"

// 普通转换(可能抛出 ClassCastException)
// val num: Int = obj as Int  // ClassCastException

// 安全转换
val num: Int? = obj as? Int  // null
val str: String? = obj as? String  // "Hello"

6.2 结合 Elvis 使用 #

kotlin
val obj: Any = "123"
val num = (obj as? Int) ?: 0

七、空安全与集合 #

7.1 可空集合 #

kotlin
val list: List<String>? = null

// 安全调用
val size = list?.size ?: 0
val first = list?.firstOrNull()

7.2 集合中的可空元素 #

kotlin
val list: List<String?> = listOf("A", null, "B", null, "C")

// 过滤非空元素
val nonNullList = list.filterNotNull()  // ["A", "B", "C"]

// 安全操作
list.map { it?.length }
list.mapNotNull { it?.length }

7.3 常用函数 #

kotlin
val list: List<String?> = listOf("A", null, "B")

// firstOrNull
list.firstOrNull()  // "A"

// lastOrNull
list.lastOrNull()   // "B"

// getOrNull
list.getOrNull(5)   // null

// find
list.find { it?.length == 1 }  // "A"

八、智能类型转换 #

8.1 自动类型转换 #

Kotlin 会自动进行智能类型转换:

kotlin
val name: String? = getName()

if (name != null) {
    // name 自动转换为 String 类型
    println(name.length)  // 不需要 ?.
}

// 类似地
when (name) {
    null -> println("Name is null")
    else -> println(name.length)  // 自动转换
}

8.2 智能转换的条件 #

kotlin
// val 局部变量
val name: String? = getName()
if (name != null) {
    println(name.length)  // 正确
}

// var 局部变量(需要确保不会被修改)
var name2: String? = getName()
if (name2 != null) {
    // 如果 name2 可能被其他线程修改,编译器会警告
    println(name2.length)
}

// 类属性(需要是 val 且没有自定义 getter)
class Person(val name: String?) {
    fun printName() {
        if (name != null) {
            println(name.length)  // 正确
        }
    }
}

九、延迟初始化与空安全 #

9.1 lateinit #

kotlin
class MainActivity {
    private lateinit var name: String
    
    fun init() {
        name = "Kotlin"
    }
    
    fun print() {
        if (::name.isInitialized) {
            println(name)
        }
    }
}

9.2 lazy #

kotlin
class Service {
    val database: Database by lazy {
        Database.connect()
    }
}

// database 不会是 null

十、最佳实践 #

10.1 避免使用 !! #

kotlin
// 不推荐
val length = name!!.length

// 推荐
val length = name?.length ?: 0

10.2 尽早处理 null #

kotlin
fun process(name: String?) {
    // 尽早返回
    val validName = name ?: return
    
    // 后续代码可以安全使用 validName
    println(validName.length)
}

10.3 使用 require/check #

kotlin
fun process(name: String?) {
    requireNotNull(name) { "Name cannot be null" }
    
    // name 自动转换为非空
    println(name.length)
}

fun process(value: Int?) {
    checkNotNull(value) { "Value cannot be null" }
    println(value)
}

10.4 使用可空性注解(与 Java 互操作) #

kotlin
// Java 代码
public class User {
    @Nullable
    public String getName() { return null; }
    
    @NotNull
    public String getId() { return "123"; }
}

// Kotlin 调用
val user = User()
val name: String? = user.name      // 识别为可空
val id: String = user.id           // 识别为非空

十一、常见模式 #

11.1 默认值模式 #

kotlin
val name = nullableName ?: "Default"
val age = nullableAge ?: 0

11.2 提前返回模式 #

kotlin
fun process(data: String?) {
    val validData = data ?: return
    // 处理 validData
}

11.3 异常模式 #

kotlin
val name = nullableName 
    ?: throw IllegalArgumentException("Name required")

11.4 let 模式 #

kotlin
nullableName?.let { name ->
    println(name.length)
    println(name.uppercase())
}

十二、总结 #

空安全操作符总结:

操作符 名称 用途
? 可空类型 声明可空类型
?. 安全调用 安全调用方法/属性
?: Elvis 提供 null 时的默认值
!! 非空断言 强制转换为非空(危险)
as? 安全转换 安全类型转换

下一步,让我们学习 类型转换

最后更新:2026-03-27