Kotlin 最佳实践
kotlin // 推荐 val name = "Alice"
// 仅在必要时 var count = 0 count++
### 1.2 字符串模板代替拼接
Kotlin 支持在字符串中直接嵌入表达式,既清晰又避免了 `+` 造成的性能开销。
```kotlin
// 好的写法
println("User: $name, age: ${user.age}")
// 避免
println("User: " + name + ", age: " + user.age)
1.3 使用 when 表达式替代冗长的 if-else if
when 既可以作为语句也可以作为表达式,配合枚举、密封类使用时尤其强大。
// 作为表达式返回结果
val description = when (statusCode) {
200 -> "OK"
404 -> "Not Found"
else -> "Unknown"
}
1.4 利用扩展函数和属性提高可读性
为既有类添加功能而不继承,让调用更自然。
fun String.isEmail(): Boolean = this.contains("@") && this.contains(".")
// 使用
if ("test@example.com".isEmail()) { ... }
1.5 优先使用标准作用域函数(apply, also, let, run, with)
这些函数可以简化对象初始化和非空操作,但要选择语义最贴切的一个:
apply:配置对象,返回对象本身。also:执行额外操作(如日志),返回对象本身。let:对非空对象执行操作并返回结果。run:配置 + 计算,返回最后一行。with:对某个对象执行多个操作,返回最后一行。
val user = User().apply {
name = "Bob"
email = "bob@mail.com"
}
val length = name?.let { it.length } ?: 0
2. 深入空安全:告别 NullPointerException
2.1 明确声明可空类型与不可空类型
在类型后加 ? 表示可空。不加 ? 的引用绝对不为 null,编译器会强制检查。
var neverNull: String = "abc"
var nullable: String? = null
2.2 安全调用操作符 ?. 和 Elvis 操作符 ?:
安全调用在对象为 null 时直接返回 null,用 Elvis 提供默认值。
val length = text?.length // 结果类型为 Int?
val realLength = text?.length ?: 0 // 结果类型为 Int
2.3 非空断言 !! 应当尽量避免
!! 如果遇到 null 会抛出异常,破坏了空安全的设计初衷。只有在极少数确信非空的场景(例如刚检查过)使用,并加上注释说明原因。
2.4 用 let 安全处理可空变量
val email: String? = getEmail()
email?.let {
// 这里 it 已经是非空的
sendTo(it)
}
2.5 善用平台类型处理 Java 互操作
来自 Java 的类型默认为平台类型(String!),既可能为可空,也可能非空。在接收时应该明确标注为可空或非空,避免隐式风险。
// Java 方法:String getName()
val name: String = javaObject.name // 假设非空
val nameOrNull: String? = javaObject.name // 接受可能为 null
3. 集合与序列操作:写出声明式、高效率代码
3.1 使用标准库函数代替手动循环
map, filter, forEach, find, groupBy, sortedBy 等函数不仅让意图一目了然,还能串联成函数链。
val adultNames = users
.filter { it.age >= 18 }
.map { it.name }
.sorted()
3.2 理解序列(Sequence)和集合的惰性求值
对于多步操作或大数据集,使用 asSequence() 可以避免中间集合的创建,提升性能。
val result = users.asSequence()
.filter { it.age >= 18 }
.map { it.name }
.take(5)
.toList() // 终端操作才执行
3.3 优先使用 firstOrNull、find 等明确的查询方法
避免 filter { ... }.first() 这种模式,直接用 first { ... } 或 firstOrNull { ... }。
val user = users.firstOrNull { it.id == targetId }
3.4 利用解构声明简化数据类操作
数据类自动支持解构。
data class Point(val x: Int, val y: Int)
val (x, y) = Point(10, 20)
4. 数据类与值的表示
4.1 使用 data class 表示纯数据类型
data class 自动生成 equals()、hashCode()、toString()、copy() 等标准方法,非常适合 DTO、模型等。
4.2 为常量分组使用 object 或枚举类
- 用
object定义无参数的常量容器。 - 用
enum class定义一组固定常量值。
object Config {
const val MAX_SIZE = 100
}
enum class Color { RED, GREEN, BLUE }
4.3 使用单例模式直接用 object 声明
Kotlin 中的 object 本身就是线程安全的单例实现。
object DatabaseConnection {
fun connect() { ... }
}
5. 函数设计最佳实践
5.1 参数使用默认值而非方法重载
Kotlin 的函数默认参数可以极大减少重载数量。
fun greet(name: String, greeting: String = "Hello") = "$greeting, $name"
5.2 使用具名参数提高可读性
当参数较多或类型相近时,具名参数让调用一目了然。
drawRectangle(x = 10, y = 20, width = 100, height = 50)
5.3 返回表达式优于代码块
当函数体只有一个表达式时,直接使用表达式体形式。
fun add(a: Int, b: Int) = a + b
5.4 高阶函数和内联函数
- 接受 Lambda 的参数放在最后,方便使用尾随 Lambda 语法。
- 对于经常调用的内联函数(尤其是 Lambda 无内部返回),考虑使用
inline减少函数对象开销。
inline fun measureTime(block: () -> Unit): Long {
val start = System.currentTimeMillis()
block()
return System.currentTimeMillis() - start
}
6. 协程:现代异步编程
6.1 使用协程而不是传统的回调或 RxJava 进行异步
协程以顺序代码风格写异步操作,避免回调地狱。
suspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {
// 网络或数据库操作
}
6.2 结构化并发:确保协程负责的结束
使用 CoroutineScope 管理协程生命周期,避免协程泄漏。在 ViewModel 或 UI 组件中使用 viewModelScope 或 lifecycleScope。
6.3 选择合适的调度器
Dispatchers.Main:主线程 UI 更新。Dispatchers.IO:网络和磁盘操作。Dispatchers.Default:CPU 密集型任务。
withContext(Dispatchers.IO) {
val data = api.fetchData()
withContext(Dispatchers.Main) { updateUI(data) }
}
6.4 正确处理异常
使用 try-catch 或 CoroutineExceptionHandler,注意协程内异常的传播规则。
viewModelScope.launch {
try {
val result = repository.load()
_state.value = Success(result)
} catch (e: Exception) {
_state.value = Error(e.message)
}
}
6.5 避免在全局作用域启动协程
GlobalScope 不绑定任何生命周期,容易引起内存泄漏和意外行为。始终将协程绑定到特定的生命周期作用域。
7. 性能与内存优化
7.1 避免不必要的对象创建
- 尽量使用基本类型数组
IntArray、LongArray而非Array<Int>以避免装箱开销。 - 字符串拼接过长时考虑
buildString { }。
7.2 合理使用 lazy 和 lateinit 延迟初始化
lazy用于只读且需要延迟计算的属性,线程安全模式可配置。lateinit用于依赖注入或@Inject等需要晚赋值的可变属性(仅限非基本类型)。
private val heavyData: Data by lazy { computeData() }
@Inject lateinit var repository: UserRepository
7.3 使用 Sequence 处理大数据管道
如上文所述,链式集合操作会产生中间集合。大集合或代价高的转换使用序列可以减少内存和计算。
7.4 inline 函数谨慎使用
内联可以减少匿名类的创建,但会增大方法体积。一般仅对接受 Lambda 的高阶函数且需要非局部返回时才使用 inline。
8. 代码风格与可读性
8.1 遵循 Kotlin 官方编码规范
- 冒号前不空格,后空格(
val x: Int) - 逗号后空格
- 函数或类的大括号不要另起一行(类头除外)
- 尽可能使用
when、try-catch等作为表达式
8.2 导入有意义的别名
当类名冲突或过长时,使用 import ... as ... 提高可读性。
import com.example.ancientdata.StrangeLongName as DataModel
8.3 统一的错误处理与日志
使用 require 和 check 进行函数前置条件检查;?: 提供默认值或抛出自定义异常。
fun prepare(value: String) {
require(value.isNotBlank()) { "value must not be blank" }
}