前置知识: Kotlin

空安全详解

00:00
2 min Beginner 2026/6/14

Kotlin空安全与智能转换

概述

空安全是 Kotlin 最重要的特性之一,通过类型系统中区分可空和不可空类型,将空指针异常从运行时提前到编译期发现。配合安全调用、Elvis 运算符和智能转换,可以优雅地处理空值问题。

基础概念

可空类型与不可空类型

var name: String = "Alice"  // 不可空类型,永远不能赋值为 null
var nick: String? = null    // 可空类型,允许 null 值

// 不可空类型不能赋值为 null
// name = null  // 编译错误

// 可空类型的成员不能直接访问
// nick.length  // 编译错误:可能为 null

空安全操作符

操作符名称说明
?.安全调用如果为 null返回 null
?:Elvis 运算符如果为 null 则使用默认值
!!非空断言如果为 null抛出 NPE
as?安全转换转换失败返回 null 而非抛出异常

快速上手

安全调用与 Elvis

var name: String? = "Alice"

// 安全调用:如果 name 为 null,整个表达式返回 null
val len: Int? = name?.length

// Elvis 运算符:为 null 时提供默认值
val length: Int = name?.length ?: 0

// 链式安全调用
val city: String? = user?.address?.city

// Elvis 配合 return/throw
val name2 = user?.name ?: return  // 为 null 时提前返回
val config = loadConfig() ?: throw IllegalStateException("配置加载失败")

非空断言

var value: String? = "Hello"

// 非空断言:断言值不为 null,否则抛出 NPE
val length: Int = value!!.length  // 如果 value 为 null 则崩溃

// 应尽量避免使用 !!,只在确定不为 null 时使用
// 错误示例:过度使用 !!
val result = getResult()!!  // 危险:可能为 null

安全转换

// as? 安全转换:失败时返回 null
val num: Int? = "123" as? Int     // null
val str: String? = 42 as? String  // null
val obj: Any = "hello"
val length = (obj as? String)?.length ?: 0  // 5

详细用法

智能转换

// 条件判断后自动智能转换
fun process(s: String?) {
    if (s != null) {
        // s 自动从 String? 转换为 String
        println(s.length)  // 无需 ?. 或 !!
    }
}

// when 表达式中的智能转换
fun describe(x: Any) = when (x) {
    is String -> x.length      // x 自动转换为 String
    is Int -> x.toDouble()     // x 自动转换为 Int
    is List<*> -> x.size       // x 自动转换为 List
    else -> "未知类型"
}

// is 检查后的智能转换
fun handle(value: Any) {
    if (value is String) {
        println(value.uppercase()) // value 自动转换为 String
    }
}

// while 循环中的智能转换
var input: String? = readLine()
while (input != null) {
    process(input) // input 自动转换为 String
    input = readLine()
}

可空类型的集合操作

val names: List<String?> = listOf("Alice", null, "Bob", null, "Charlie")

// 过滤 null 值
val nonNull: List<String> = names.filterNotNull()
// ["Alice", "Bob", "Charlie"]

// 映射并过滤 null
val lengths: List<Int> = names.mapNotNull { it?.length }
// [5, 3, 7]

// forEach 中处理 null
names.forEach { name ->
    name?.let {
        println("名字: $it, 长度: ${it.length}")
    }
}

let、run、with 处理空值

// let:对非空值执行操作
val name: String? = getName()
name?.let {
    // it 为 String 类型(非空)
    println("名字: $it, 长度: ${it.length}")
}

// run:对非空值执行代码块
name?.run {
    // this 为 String 类型
    println("名字: $this, 长度: ${length}")
}

// takeIf / takeUnless:条件判断
val validName = name?.takeIf { it.length > 2 }
val invalidName = name?.takeUnless { it.isBlank() }

常见场景

API 响应处理

// 处理可能为 null 的 API 响应
fun handleResponse(response: ApiResponse?) {
    val data = response?.data ?: run {
        println("响应为空")
        return
    }

    val userName = data.user?.name ?: "未知用户"
    val userEmail = data.user?.email ?: "无邮箱"

    println("用户: $userName, 邮箱: $userEmail")
}

链式空安全调用

// 深层嵌套对象的空安全访问
val street = company?.department?.manager?.address?.street ?: "未知地址"

// 等价于 Java 的:
// if (company != null && company.getDepartment() != null && ...)

可空类型与 Java 互操作

// Java 返回值可能为 null,使用平台类型
val javaResult = javaService.getData()  // 类型为 String!(平台类型)

// 明确标记为可空
val safeResult: String? = javaService.getData()

// 使用 @Nullable 和 @NonNull 注解帮助 Kotlin 推断
// Java: @Nullable String getName()
// Kotlin 自动识别为 String?

注意事项

  • 优先使用 ?. 和 ?: 处理,避免使用 !!
  • 智能转换局部变量和不可变属性有效可变属性可能被其他线程修改
  • 集合的 filterNotNull() 返回不可空类型列表
  • 与 Java 互操作时,平台类型(Type!)需要手动处理安全
  • lateinit 适用于延迟初始化的不可空属性,但只能用于 var
  • 空类型泛型参数需要注意List<String?> 和 List 是不同类型

进阶用法

自定义空安全逻辑

// 使用 require 检查参数非空
fun processName(name: String?) {
    requireNotNull(name) { "姓名不能为空" }
    // 此处 name 自动转换为 String(非空)
    println(name.length)
}

// 使用 check 检查状态
fun getState(): String {
    val state = currentState ?: throw IllegalStateException("状态未初始化")
    return state
}

// 委托属性处理空值
class Config {
    private var _value: String? = null
    val value: String
        get() = _value ?: throw IllegalStateException("配置未加载")
}

可空类型与协程

// 协程中处理空值
suspend fun fetchUser(id: String): User? {
    return try {
        apiService.getUser(id)
    } catch (e: Exception) {
        null
    }
}

// 使用
viewModelScope.launch {
    val user = fetchUser(userId)
    user?.let {
        updateUI(it)
    } ?: run {
        showError("用户不存在")
    }
}

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式