Kotlin 空安全
Kotlin 在语言层面解决了空指针异常(NullPointerException)问题。这是 Kotlin 最重要的特性之一。
可空类型
基本概念
在 Kotlin 中,类型默认不允许为空:
fun main() {
// 普通类型 - 不能为空
var name: String = "Kotlin"
// name = null // 编译错误!
// 可空类型 - 使用 ? 标记
var nullableName: String? = "Kotlin"
nullableName = null // 正确
}
可空类型的场景
fun main() {
// 1. 从函数返回可空值
fun findUser(id: Int): String? {
return if (id > 0) "User$id" else null
}
// 2. 从 Map 获取值
val map = mapOf("a" to 1, "b" to 2)
val value: Int? = map["c"] // key 不存在,返回 null
// 3. Java 互操作(Java 类型在 Kotlin 中默认平台类型)
// val javaString: String = getFromJava() // 可能为 null
}
安全调用
?. 操作符
安全调用在对象为 null 时返回 null,不会抛出异常:
class Person(val name: String, val company: Company?)
class Company(val name: String)
fun main() {
val person = Person("Tom", Company("Google"))
// 普通调用(可能 NPE)
// println(person.company.name) // 如果 company 为 null,会 NPE
// 安全调用
println(person.company?.name) // Google
// 如果 company 为 null,返回 null
val person2 = Person("Tom", null)
println(person2.company?.name) // null
// 链式调用
println(person.company?.name?.length) // 6
}
完整示例
class Address(val street: String?, val city: String?)
class Person(val name: String, val address: Address?)
fun main() {
val person = Person("Tom", Address("Main St", "Beijing"))
// 链式安全调用
val city = person.address?.city
println(city) // Beijing
val person2 = Person("Jerry", null)
val city2 = person2.address?.city
println(city2) // null
val person3 = Person("Mike", Address(null, "Shanghai"))
val street = person3.address?.street
println(street) // null
}
Elvis 操作符
?: 操作符
如果左侧表达式不为 null,返回其值;否则返回右侧表达式:
fun main() {
val name: String? = null
// 使用 Elvis 操作符
val displayName = name ?: "Unknown"
println(displayName) // Unknown
// 常用场景:提供默认值
fun getName(user: User?): String {
return user?.name ?: "Guest"
}
println(getName(null)) // Guest
}
Elvis 与 return/throw
fun findUser(id: Int): String {
val user = findUserById(id)
// 如果为 null,直接返回
return user ?: return "User not found"
}
fun process(value: String?) {
val processed = value ?: throw IllegalArgumentException("Value cannot be null")
println(processed)
}
fun getFirst(list: List<Int>): Int {
return list.firstOrNull() ?: 0
}
非空断言
!! 操作符
将可空类型转换为非空类型,如果为 null 则抛出异常:
fun main() {
val name: String? = "Kotlin"
// 使用 !! 断言非空
println(name!!) // Kotlin
// 如果为 null,会抛出 NullPointerException
val name2: String? = null
// println(name2!!) // 抛出 NPE
}
何时使用
// 适用于:
// 1. 你确信某个值不可能为 null
// 2. 与 Java 互操作,Java 代码保证不为 null
// 不推荐:
// val name = getName()?.trim()!!
// // 如果 getName() 返回 null,会在运行时崩溃
最佳实践:尽量避免使用 !!,优先使用安全调用和 Elvis 操作符。
安全类型转换
as? 操作符
安全类型转换,转换失败返回 null:
fun main() {
val obj: Any = "Hello"
// 普通类型转换(可能 ClassCastException)
// val str: String = obj as String
// 安全类型转换
val str: String? = obj as? String
println(str) // Hello
// 转换失败
val obj2: Any = 123
val str2: String? = obj2 as? String
println(str2) // null
}
let 函数
let 简介
let 函数在对象非空时执行代码块:
fun main() {
val name: String? = "Kotlin"
// 普通写法
if (name != null) {
println(name.length)
}
// 使用 let
name?.let {
println(it.length) // 6
}
// name 为 null 时不执行
val name2: String? = null
name2?.let {
println(it.length) // 不执行
}
}
实际应用
// 1. 链式调用
fun processUser(user: User?) {
user?.let {
println("Processing user: ${it.name}")
it.validate()
it.save()
}
}
// 2. 与 Elvis 结合
fun getLength(text: String?): Int {
return text?.let { it.length } ?: 0
}
// 3. 避免 if-null 检查
email?.let { sendEmail(it) }
空合并与条件执行
also 和 apply
fun main() {
// also - 执行额外操作,返回原对象
val numbers = mutableListOf(1, 2, 3)
numbers.also {
println("Adding numbers: $it")
}.add(4)
println(numbers) // [1, 2, 3, 4]
// apply - 配置对象,返回原对象
val person = Person("Tom", null).apply {
address = Address("Main St", "Beijing")
}
println(person.address?.city) // Beijing
}
可空类型集合
可空 vs 空集合
fun main() {
// 可空集合:集合本身可以为 null
val nullableList: List<Int>? = null
// 元素可空:集合中的元素可能为 null
val listWithNulls: List<Int?> = listOf(1, null, 3)
// 处理
println(nullableList?.size) // null
println(listWithNulls.size) // 3
// 过滤非空元素
val filtered = listWithNulls.filterNotNull()
println(filtered) // [1, 3]
}
可空类型的比较
== vs ===
fun main() {
val a: String? = "Kotlin"
val b: String? = "Kotlin"
val c: String? = null
// == 比较值
println(a == b) // true
println(a == c) // false
// === 比较引用
println(a === b) // true(字符串字面量优化)
val d = String("Kotlin".toCharArray())
val e = String("Kotlin".toCharArray())
println(d == e) // true
println(d === e) // false
}
平台类型
Java 互操作
从 Java 代码获取的值是"平台类型":
// Java 代码
// public class User {
// public String name; // 可能为 null
// }
// Kotlin 中使用(平台类型)
fun main() {
val user = User()
// 编译器不会阻止你进行任何操作
println(user.name.length) // 可能 NPE
// 可以声明为可空类型
val name: String? = user.name
}
@Nullable 和 @NotNull
// Java 中使用注解
public class User {
@Nullable
public String name; // Kotlin 中为 String?
@NotNull
public String email; // Kotlin 中为 String
}
空安全最佳实践
1. 优先使用非空类型
// 推荐
fun greet(name: String) = println("Hello, $name")
// 不推荐
fun greet(name: String?) = println("Hello, ${name ?: "Unknown"}")
2. 使用 lateinit 延迟初始化
class Person {
lateinit var name: String
fun initialize() {
name = "Tom"
}
fun printName() {
// 使用前确保已初始化
if (::name.isInitialized) {
println(name)
}
}
}
3. 使用 require 和 check
fun process(input: String?) {
// 检查参数
requireNotNull(input) { "Input cannot be null" }
// 检查状态
check(isValid) { "Invalid state" }
// 继续处理
println(input.length)
}
4. 使用 ?: 返回或断言
fun findUser(id: Int): User? = ...
// 返回默认值
fun getDisplayUser(id: Int): String {
return findUser(id)?.name ?: "Guest"
}
// 继续传播
fun getUserName(id: Int): String {
return findUser(id)?.name ?: return "User not found"
}
小结
本章我们学习了:
- 可空类型:
?标记可为 null 的类型 - 安全调用:
?.避免 NPE - Elvis 操作符:
?:提供默认值 - 非空断言:
!!强制非空(慎用) - 安全转换:
as?安全类型转换 - let 函数:在非空时执行代码
- also/apply:链式操作
- 平台类型:Java 互操作
- 最佳实践:避免 NPE 的原则
练习
- 创建一个函数,接受可空的字符串,返回大写形式(null 时返回空字符串)
- 使用 let 函数处理可空用户,调用其方法
- 使用 Elvis 操作符和 throw 组合
- 创建一个 Person 类,包含可选的 Address,使用安全调用获取城市
- 将现有代码重构,移除所有 !! 的使用