name: Kotlin Null Safety user-invocable: false description: 使用Kotlin的空安全系统,包括可空类型、安全调用、Elvis操作符、智能转换和模式,以消除NullPointerException,同时保持代码的表达性和清晰度。 allowed-tools: []
Kotlin 空安全
引言
Kotlin的空安全系统通过在类型系统中区分可空和非空类型,在编译时消除NullPointerException。这种方法使空处理变得明确,并迫使开发人员有意识地处理潜在的空值。
与Java中任何引用都可以为空不同,Kotlin要求使用?操作符显式声明可空性。编译器在解引用可空值之前强制执行空检查,防止了困扰Java应用程序的大多数空相关崩溃。
本技能涵盖可空类型、安全调用操作符、智能转换、泛型类型中的可空性,以及设计空安全API的模式,同时保持代码清晰度。
可空类型
可空类型明确表示变量或属性可以持有空值,而非可空类型提供编译时非空值的保证。
// 非可空类型
var name: String = "Alice"
// name = null // 编译错误
// 可空类型
var nullableName: String? = "Bob"
nullableName = null // 正确
// 函数参数
fun greet(name: String) {
println("Hello, $name")
}
fun greetNullable(name: String?) {
if (name != null) {
println("Hello, $name")
} else {
println("Hello, guest")
}
}
// greet(null) // 编译错误
greetNullable(null) // 正确
// 可空返回类型
fun findUser(id: Int): User? {
return if (id > 0) User(id, "Alice") else null
}
data class User(val id: Int, val name: String)
// 可空属性
class Person(
val name: String,
val email: String?,
var phoneNumber: String?
)
// 带有可空元素的集合
val nullableList: List<String?> = listOf("A", null, "B")
val listOfNullable: List<String>? = null
// 来自Java的平台类型(String!)
// 在Kotlin中视为可空以确保安全
// 可空this
class Service {
fun process() {
val self: Service? = this
self?.validate()
}
fun validate() {}
}
?后缀使类型可空。非可空类型不能在不显式声明可空性的情况下赋值为空,防止了意外的空引用。
安全调用操作符
安全调用操作符?.安全地访问可空引用的属性和方法,如果接收者为空则返回空而不是抛出NPE。
// 基本安全调用
val name: String? = "Alice"
val length: Int? = name?.length
val nullName: String? = null
val nullLength: Int? = nullName?.length // 返回空
// 链式安全调用
data class Address(val street: String?, val city: String?)
data class Company(val address: Address?)
data class Employee(val company: Company?)
val employee: Employee? = Employee(Company(Address("Main St", "NYC")))
val city: String? = employee?.company?.address?.city
println(city) // "NYC"
val nullEmployee: Employee? = null
val nullCity: String? = nullEmployee?.company?.address?.city
println(nullCity) // 空
// 带方法的安全调用
fun processUser(user: User?) {
user?.let { u ->
println("Processing ${u.name}")
}
}
// 带扩展函数的安全调用
fun String?.orDefault(default: String): String {
return this ?: default
}
val result = nullName?.orDefault("Unknown")
// 表达式中的安全调用
class Profile(val bio: String?)
fun displayBio(profile: Profile?) {
val bioLength = profile?.bio?.length ?: 0
println("Bio length: $bioLength")
}
// 带可变属性的安全调用
class Container {
var value: String? = null
fun updateValue() {
value?.let { current ->
value = current.uppercase()
}
}
}
安全调用链在第一个空处短路,使深度嵌套的可选访问变得干净安全,无需多次空检查。
Elvis操作符和空合并
Elvis操作符?:为空表达式提供默认值,实现简洁的后备逻辑,无需冗长的if-else语句。
// 基本Elvis操作符
val name: String? = null
val displayName = name ?: "Guest"
println(displayName) // "Guest"
// 带安全调用的Elvis
fun getUserCity(employee: Employee?): String {
return employee?.company?.address?.city ?: "Unknown"
}
// 带表达式的Elvis
fun calculateTotal(subtotal: Double?, taxRate: Double?): Double {
val sub = subtotal ?: 0.0
val tax = taxRate ?: 0.15
return sub * (1 + tax)
}
// 带返回/抛出的Elvis
fun requireName(name: String?): String {
return name ?: throw IllegalArgumentException("Name required")
}
fun processUser(user: User?) {
val u = user ?: return
println("Processing ${u.name}")
}
// 链式Elvis操作符
fun findValidValue(
primary: String?,
secondary: String?,
tertiary: String?
): String {
return primary ?: secondary ?: tertiary ?: "default"
}
// 带可空属性的Elvis
class Config {
var timeout: Int? = null
fun getTimeout(): Int {
return timeout ?: 30000
}
}
// 构造函数中的Elvis
class Service(name: String?) {
val serviceName: String = name ?: "DefaultService"
}
// 带函数调用的Elvis
fun fetchFromCache(): String? = null
fun fetchFromNetwork(): String? = "data"
fun getData(): String {
return fetchFromCache() ?: fetchFromNetwork() ?: "fallback"
}
Elvis操作符仅在左侧为空时评估右侧,支持默认值的惰性评估。
智能转换
智能转换在空检查后自动将可空类型转换为非可空类型,消除冗余转换并提高代码清晰度。
// 空检查后的智能转换
fun printLength(text: String?) {
if (text != null) {
println(text.length) // text智能转换为String
}
}
// 表达式中的智能转换
fun processName(name: String?): Int {
return if (name != null) {
name.length // 智能转换
} else {
0
}
}
// 带返回的智能转换
fun requireUser(user: User?): User {
if (user == null) {
throw IllegalStateException("User required")
}
return user // 智能转换为User
}
// 带Elvis的智能转换
fun getLength(text: String?): Int {
val nonNull = text ?: return 0
return nonNull.length // 智能转换
}
// 带when表达式的智能转换
fun describe(obj: Any?): String {
return when {
obj == null -> "null"
obj is String -> "String of length ${obj.length}"
obj is Int -> "Int: $obj"
else -> "Unknown type"
}
}
// 带let的智能转换
fun processNullable(value: String?) {
value?.let { nonNull ->
println(nonNull.uppercase()) // nonNull是String
}
}
// 智能转换限制
class Container(var value: String?) {
fun process() {
// 无法智能转换var属性
if (value != null) {
// println(value.length) // 编译错误
}
// 使用局部变量进行智能转换
val localValue = value
if (localValue != null) {
println(localValue.length) // 正确
}
}
}
// 带合约的智能转换
fun requireNotNull(value: String?) {
require(value != null)
println(value.length) // 需求后的智能转换
}
智能转换适用于不可变变量和val属性,但不适用于var属性,因为var可能在检查和用法之间改变。
非空断言和平台类型
非空断言操作符!!如果值为空则显式抛出NPE,适用于值不可能为空但编译器无法验证的情况。
// 非空断言操作符
fun processName(name: String?) {
val length = name!!.length // 如果name为空则抛出NPE
println("Length: $length")
}
// !!的使用案例
fun initializeFromConfig(config: Map<String, String>) {
// 我们知道这些键存在
val apiKey = config["api_key"]!!
val endpoint = config["endpoint"]!!
println("Configured with $apiKey at $endpoint")
}
// 避免链式!!
val city = employee!!.company!!.address!!.city!! // 不好
// 更好的替代方案
val city2 = employee?.company?.address?.city
?: throw IllegalStateException("City required")
// 来自Java的平台类型
class JavaInterop {
fun useJavaApi() {
val javaString = JavaClass.getString() // String!(平台类型)
// 视为可空以确保安全
val length: Int? = javaString?.length
// 或断言非空
val length2: Int = javaString!!.length
// 或添加显式类型
val explicitString: String = JavaClass.getString()
}
}
// 测试中的平台类型模拟
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
fun mockJavaString(): java.lang.String? {
return null
}
// lateinit用于非空延迟初始化
class Service {
lateinit var apiClient: ApiClient
fun initialize(client: ApiClient) {
apiClient = client
}
fun makeRequest() {
if (::apiClient.isInitialized) {
apiClient.request()
}
}
}
class ApiClient {
fun request() {}
}
// 谨慎使用!!并记录假设
fun parseJson(json: String?): Data {
// !!在此处正确,因为调用者确保非空
return Json.parse(json!!)
}
object Json {
fun parse(json: String): Data = Data()
}
data class Data(val value: String = "")
class JavaClass {
companion object {
@JvmStatic
fun getString(): String = ""
}
}
!!操作符应谨慎使用,仅在您高度自信值非空时使用,因为它重新引入了崩溃风险。
集合和泛型中的可空性
集合和泛型类型支持容器和元素级别的可空性,需要清楚区分可空元素和可空集合。
// 可空元素 vs 可空集合
val listWithNulls: List<String?> = listOf("A", null, "B")
val nullableList: List<String>? = null
// 处理可空元素
fun filterNulls(items: List<String?>): List<String> {
return items.filterNotNull()
}
val filtered = filterNulls(listOf("A", null, "B"))
println(filtered) // [A, B]
// 可空映射值
val userIds: Map<String, Int?> = mapOf(
"alice" to 1,
"bob" to null,
"charlie" to 3
)
fun getUserId(name: String): Int? {
return userIds[name]
}
// 带可空类型的泛型函数
fun <T> firstOrNull(list: List<T>): T? {
return list.firstOrNull()
}
// 带可空性的泛型约束
fun <T : Any> nonNullable(value: T) {
// T不能为空
println(value.toString())
}
// 可空泛型类型
class Container<T>(val value: T?)
val stringContainer = Container<String>(null)
val intContainer = Container<Int>(42)
// 可空类型上的扩展函数
fun <T> List<T?>.filterNotNullAndMap(transform: (T) -> String): List<String> {
return this.filterNotNull().map(transform)
}
val result = listOf(1, null, 3).filterNotNullAndMap { it.toString() }
// 可空接收器
fun String?.isNullOrEmpty(): Boolean {
return this == null || this.isEmpty()
}
val empty: String? = null
println(empty.isNullOrEmpty()) // true
// 带可空元素的序列
fun processSequence(items: Sequence<String?>) {
items
.filterNotNull()
.map { it.uppercase() }
.forEach { println(it) }
}
// 泛型方差和可空性
interface Producer<out T> {
fun produce(): T?
}
interface Consumer<in T> {
fun consume(item: T?)
}
理解List<String?>(可空字符串列表)和List<String>?(可空列表)之间的区别对于正确的空处理至关重要。
设计空安全API
设计具有适当可空性的API提高了可用性,并通过在类型系统中明确空期望防止误用。
// 优先使用非可空参数
class UserRepository {
fun save(user: User) {
// user保证非空
println("Saving ${user.name}")
}
}
// 使用可空返回处理可选值
class UserService {
fun findById(id: Int): User? {
return if (id > 0) User(id, "Alice") else null
}
fun getAllUsers(): List<User> {
// 返回空列表,而非空
return emptyList()
}
}
// 带可空累积的构建器模式
class QueryBuilder {
private var table: String? = null
private var where: String? = null
private var orderBy: String? = null
fun from(table: String) = apply { this.table = table }
fun where(condition: String) = apply { this.where = condition }
fun orderBy(column: String) = apply { this.orderBy = column }
fun build(): String {
val t = table ?: throw IllegalStateException("Table required")
val w = where?.let { "WHERE $it" } ?: ""
val o = orderBy?.let { "ORDER BY $it" } ?: ""
return "SELECT * FROM $t $w $o".trim()
}
}
// 带默认值的可空配置
data class Config(
val timeout: Int? = null,
val retries: Int? = null,
val debug: Boolean? = null
) {
fun getTimeout() = timeout ?: 30000
fun getRetries() = retries ?: 3
fun isDebug() = debug ?: false
}
// 用于可选值的密封类
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
object NotFound : Result<Nothing>()
}
fun findUser(id: Int): Result<User> {
return when {
id < 0 -> Result.Error("Invalid ID")
id == 0 -> Result.NotFound
else -> Result.Success(User(id, "Alice"))
}
}
// 带可空返回的验证
class Validator {
fun validateEmail(email: String): String? {
return if (email.contains("@")) {
null // 无错误
} else {
"Invalid email format"
}
}
}
// 可空回调参数
class AsyncLoader {
fun load(
onSuccess: (User) -> Unit,
onError: ((String) -> Unit)? = null
) {
try {
val user = User(1, "Alice")
onSuccess(user)
} catch (e: Exception) {
onError?.invoke(e.message ?: "Unknown error")
}
}
}
良好的API设计尽可能减少可空性,使用空集合而非空列表,并使用密封类提供更丰富的可选值语义。
最佳实践
-
默认优先使用非可空类型,以最大化编译时安全性并减少整个代码库中的空检查。
-
使用安全调用操作符
?.进行链式调用,而非多个空检查,以保持代码简洁可读。 -
使用Elvis操作符
?:提供默认值,而非冗长的if-else链,用于简单的后备场景。 -
返回空集合而非空值,以简化客户端代码并消除集合结果的空检查。
-
在空检查后利用智能转换,避免冗余转换,让编译器跟踪非空保证。
-
最小化使用
!!操作符,并在使用时记录假设,因为它重新引入了崩溃风险。 -
设计具有明确可空性的API,清楚传达意图并防止可空值的误用。
-
使用lateinit进行非空延迟初始化,而非手动空检查的可空属性。
-
应用可空扩展,提供像
isNullOrEmpty()这样的实用工具,以在常见场景中实现更清洁的空处理。 -
优先使用密封类而非可空类型,在表示成功、错误和缺失状态时提供更丰富的语义。
常见陷阱
-
过度使用可空类型,当值永远不应为空时,使API更难用并添加不必要的检查。
-
链式
!!操作符创建了不明确的崩溃点;使用安全调用或显式验证代替。 -
忽略来自Java的平台类型可能导致NPE;将Java返回值视为可空,除非有文档说明。
-
使用var属性进行智能转换失败,因为var可能改变;使用局部val副本进行智能转换。
-
返回空集合而非空集合迫使客户端处理两个不必要的独立情况。
-
未考虑equals/hashCode中的可空性可能导致集合和比较中的意外行为。
-
忘记安全调用返回可空类型导致意外空值在代码中传播。
-
使用可空主构造函数参数而无默认值使对象创建不必要地复杂。
-
创建深度嵌套的可空结构变得难以处理;为清晰度进行扁平化或使用密封类。
-
未在复杂API中记录空语义使调用者猜测空值何时有效或代表什么。
何时使用此技能
在构建任何Kotlin应用程序时使用Kotlin空安全,以消除NullPointerException并使空处理明确,包括Android应用、服务器端服务和多平台项目。
当处理来自外部源如网络API、数据库或用户输入的数据时应用可空类型和安全调用操作符,这些值可能缺失。
在处理可选配置、默认值或后备值时使用Elvis操作符和智能转换,以保持代码简洁可读。
在构建库或框架时利用空安全设计模式,创建难以误用并清楚传达期望的API。
在领域模型中空值不足以表示不同状态时使用密封类和Result类型提供更丰富的可选语义。