引言:为什么聪明的开发者也会在 Kotlin 上栽跟头
Kotlin 是一种优雅且强大的语言,赢得了数百万开发者的青睐。但它的简洁性和灵活性也暗藏陷阱,尤其是对于从 Java 或其他语言转来的初学者。许多人认为既然 Kotlin 比 Java “更容易理解”,那么错误就会更少。实际上恰恰相反:不理解语言哲学会导致难以排查的 bug。
在本文中,我们将分析 Kotlin 初学者常见错误——从忽视 null safety 到错误使用协程。每个错误都会配有代码示例和实用建议,帮助您避免它们。
1. Null Safety:何时“?”能救命,何时不能
1.1. 忽视可空类型
最常见的错误是在本可以使用安全调用的地方使用 !!(非空断言)。初学者经常这样写:
fun printLength(str: String?) { println(str!!.length) // 如果 str == null,会抛出 NPE}正确的方法是使用 ?.let 或 Elvis 操作符 ?::
fun printLength(str: String?) { str?.let { println(it.length) } ?: println("字符串为 null")}1.2. 忘记智能转换
Kotlin 会在 null 检查后自动进行类型转换,但初学者往往没有注意到这一点:
fun process(value: Any?) { if (value is String) { // value 已经是 String 类型,无需强制转换! println(value.length) // 无需写成 (value as String).length }}建议:相信编译器。只有在 100% 确定值不为 null 且准备好因错误而崩溃时,才使用 !!。
2. Sealed Class 和 When:穷尽性检查的陷阱
2.1. 在 sealed class 中使用 else 分支
Sealed class 的设计目的是让编译器检查所有分支是否都已处理。但初学者经常添加 else,从而破坏了这一特性:
sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result()}
fun handleResult(result: Result) { when (result) { is Result.Success -> println(result.data) is Result.Error -> println(result.message) else -> {} // 错误!如果添加新的子类,编译器不会警告 }}正确做法是 不使用 else:
fun handleResult(result: Result) { when (result) { is Result.Success -> println(result.data) is Result.Error -> println(result.message) } // 如果未覆盖所有分支,编译器会报错}2.2. 混淆 sealed class 和 enum
初学者经常在需要 sealed class 的地方选择 enum。请记住:enum 用于有限的常量集合,sealed class 用于每个子类包含不同数据的层次结构。
3. 作用域函数:apply、let、run、with、also——如何避免混淆
3.1. 混淆上下文:this vs it
最常见的混淆是初学者在需要使用 let(上下文为 it)的操作中使用了 apply(上下文为 this):
data class User(var name: String, var age: Int)
fun initUser(): User { return User("匿名", 0).apply { // this 指向 User name = "伊万" age = 25 }}
fun printUserInfo(user: User?) { user?.let { // it 指向 user println("姓名: ${it.name}, 年龄: ${it.age}") }}3.2. 无意义的作用域函数链
初学者喜欢编写类似这样的链式调用:
list?.let { it.filter { it > 5 }.also { println(it) } }