跳到主要内容

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"
}

小结

本章我们学习了:

  1. 可空类型? 标记可为 null 的类型
  2. 安全调用?. 避免 NPE
  3. Elvis 操作符?: 提供默认值
  4. 非空断言!! 强制非空(慎用)
  5. 安全转换as? 安全类型转换
  6. let 函数:在非空时执行代码
  7. also/apply:链式操作
  8. 平台类型:Java 互操作
  9. 最佳实践:避免 NPE 的原则

练习

  1. 创建一个函数,接受可空的字符串,返回大写形式(null 时返回空字符串)
  2. 使用 let 函数处理可空用户,调用其方法
  3. 使用 Elvis 操作符和 throw 组合
  4. 创建一个 Person 类,包含可选的 Address,使用安全调用获取城市
  5. 将现有代码重构,移除所有 !! 的使用