空安全详解
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("用户不存在")
}
}