ZetCode

Kotlin 可空值

最后修改日期:2025 年 3 月 22 日

Kotlin 的类型系统驯服了空指针异常的野兽,将类型分为可空和不可空两种。本教程深入探讨如何使用安全调用、Elvis 运算符等方法来处理空值,通过实用、真实的例子,确保您的代码不会崩溃。

可空类型

默认情况下,Kotlin 将变量锁定为不可空值—添加一个 ? 即可将 null 作为一个选项解锁。这就像一个安全开关,当 null 可能会潜入时,您可以打开它。

NullableTypes.kt
fun main() {
    var username: String = "Alice"  // Locked: no null allowed
    // username = null  // Error: Null can't crash this party

    var email: String? = "bob@site.com"  // Unlocked: null's welcome
    email = null  // All good here

    println(email)  // Output: null
}

username 保持不可空,而 email 通过 ? 接受 null。想想用户资料—电子邮件可能是可选的,但用户名是必需的。Kotlin 在编译时强制执行这种区分。

安全调用

安全调用运算符 (?.) 绕过 null,仅在对象存在时才获取属性或方法—否则,它会耸耸肩并返回 null,不会发脾气。

SafeCalls.kt
fun main() {
    val bio: String? = null
    val charCount = bio?.length

    println(charCount)  // Output: null
}

bio?.length 检查可空的 bio,而不会冒崩溃的风险。想象一个社交媒体应用程序—一些用户会跳过 bio,而 ?. 保持应用程序的正常运行,返回 null 而不是崩溃。

Elvis 运算符

当可空值变为空时,Elvis 运算符 (?:) 迅速出现,提供一个回退值,使您的代码保持在正轨上,并提供一个默认值。

ElvisOperator.kt
fun main() {
    val nickname: String? = null
    val displayName = nickname ?: "Guest"

    println(displayName)  // Output: Guest
}

如果 nicknamenullnickname ?: "Guest" 则选择 "Guest"。想象一个论坛—没有昵称的用户会得到一个友好的默认值,这要感谢 Elvis 的巧妙救援。

安全类型转换

安全类型转换运算符 (as?) 尝试进行类型切换而无需戏剧性——如果失败,您将得到 null 而不是 ClassCastException

SafeCasts.kt
fun main() {
    val input: Any = "42"
    val count: Int? = input as? Int

    println(count)  // Output: null
}

input as? Int 尝试将字符串转换为整数,安全地转换为 null。想想解析用户输入—数字可能会以文本形式到达,而 as? 使管道保持流畅运行而不会崩溃。

非空断言

非空断言 (!!) 关闭了 null 的大门,将可空类型强制转换为非空类型—但如果 null 溜了进来,就会发生 NullPointerException 爆炸。

NonNullAssertion.kt
fun main() {
    val token: String? = null
    val length = token!!.length  // Boom! NullPointerException

    println(length)  // Never reached
}

token!!.length 押注 token 不为 null—在这里,它输得很惨。仅当您 100% 确定令牌存在时,才在身份验证系统中使用它;否则,它就是一颗定时炸弹。

可空集合

集合可以包含可空类型,而像 filterNotNull 这样的工具会清除 null,只留下好的东西。

NullableCollections.kt
fun main() {
    val scores: List = listOf(95, null, 87, null, 91)
    val validScores = scores.filterNotNull()

    println(validScores)  // Output: [95, 87, 91]
}

filterNotNullscores 中清除 null。想象一个测验应用程序—一些答案可能未评分 (null),并且这会清理列表以进行最终统计,无需大惊小怪。

使用可空类型的 Let 函数

let 函数与可空类型一起使用,仅在值存在时运行一个代码块—将其视为一个保镖,只允许非空值进入俱乐部。

LetFunction.kt
fun main() {
    val address: String? = "123 Main St"
    address?.let {
        println("Shipping to: $it")  // Output: Shipping to: 123 Main St
    }

    val noAddress: String? = null
    noAddress?.let {
        println("Shipping to: $it")  // No output
    }
}

address?.let 仅处理非空地址。在电子商务结账中,这确保了仅在存在送货详细信息时才处理它们—安全且有选择性。

链式安全调用

安全调用可以链接,像专业人士一样导航嵌套的可空类型,如果任何链接中断,则返回 null

ChainingSafeCalls.kt
data class User(val profile: Profile?)
data class Profile(val city: String?)

fun main() {
    val user: User? = User(null)
    val cityLength = user?.profile?.city?.length

    println(cityLength)  // Output: null
}

user?.profile?.city?.length 深入挖掘多层,在第一个 null 处退出。在旅行应用程序中,仅当用户和个人资料存在时,才会获取城市名称的长度—安全而流畅。

使用多个回退的空合并

Elvis 运算符可以堆叠回退值,在紧急情况下选择第一个非空值,就像一个备用计划,还有一个备用计划。

NullCoalescing.kt
fun main() {
    val primaryPhone: String? = null
    val backupPhone: String? = null
    val defaultPhone = "N/A"
    val contact = primaryPhone ?: backupPhone ?: defaultPhone

    println(contact)  // Output: N/A
}

primaryPhone ?: backupPhone ?: defaultPhone 寻找有效的电话,最终结果是 "N/A"。在联系人管理器中,这确保您始终有内容可以显示,无论数据有多么稀疏。

结合 Let 和 Elvis

混合使用 let?: 可以创建一个紧密的空值处理流程,处理非空值,并为其他值提供回退。

LetAndElvis.kt
fun main() {
    val coupon: String? = null
    val discount = coupon?.let { it.toInt() } ?: 0

    println("Discount: $discount%")  // Output: Discount: 0%
}

如果存在,coupon?.let { it.toInt() } ?: 0 将优惠券代码转换为整数,否则默认为 0。在购物车中,这仅在代码有效时才应用折扣—干净且防崩溃。

处理可空值的最佳实践

来源

Kotlin 空安全文档

本教程揭开了 Kotlin 空安全魔法的面纱,通过实用的角度展示了安全调用和 let 等工具。通过这些技巧,您将躲避空值灾难,并保持您的代码像岩石一样稳定。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经写了 1400 多篇文章和 8 本电子书。我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程