
在上一篇文章中,我们介绍了 Kotlin 的一些基础最佳实践,如不可变性、空安全、数据类、协程和密封类。 现在,让我们更进一步。
本文中,我们将探讨 Kotlin 的高级最佳实践,这些实践有助于你为生产级应用编写更简洁、更安全、更高效的代码。
依然,声明一下本文的主旨:本文并非一份系统性的编程教程,而是我多年来编写 Kotlin 代码的一些经验总结。可能你和我会有不同的感受,毕竟每个人踩过的坑不一样,走的路也不尽相同,权当是一位 Kotlin 开发者的心得闲谈吧。
内联高阶函数
高阶函数可能会带来额外开销。当你将函数作为参数传递时,使用 inline
关键字,以避免不必要的对象创建并提高性能。
Kotlin
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println("Took ${System.currentTimeMillis() - start} ms")
}
不仅如此,这种技巧对于日志打印也尤为重要:
Kotlin
inline fun logD(tag: String, content: () -> String) {
if (Debug) {
println("$tag: ${content()}")
}
}
logD("Main") { "User clicked $bird" }
传统的日志打印方式,无论日志是否最终输出,都会产生字符串拼接的开销。
如果采用内联高阶函数的方式,不仅省去了拼接字符串的开销,还能省去高阶函数的额外开销。
善用类型别名
如果函数/类签名冗长且重复,使用类型别名来简化它们:
Kotlin
typealias Callback = (Boolean, String?) -> Unit // 简化冗长的 lambda 表达式
fun loginUser(username: String, password: String, callback: Callback) {
// ...
}
typealias UserOrderMap = Map<String, OrderList> // 简化冗长的泛型集合
这会使你的 API 更加简洁,更易于阅读。
合理使用 const 和 @JvmField
- 对于编译时常量,使用
const val
。 - 当 Java 互操作需要字段访问时,在伴生对象中使用
@JvmField
。
Kotlin
class ApiConfig {
companion object {
const val TIMEOUT = 30_000
}
}
class Blog {
@JvmField
val id: Int = 123
}
在 Java 中访问时:
Java
System.out.println(ApiConfig.TIMEOUT);
Blog blog = new Blog();
System.out.println(blog.id);
而如果没有加 @JvmField
的话,在 Java 中访问是这样的:
Java
// ...
System.out.println(blog.getId());
利用默认参数和命名参数
不要创建多个重载方法,而是使用默认参数:
Kotlin
fun connect(host: String, port: Int = 8080, useSSL: Boolean = false) { ... }
connect(host = "example.com") // 默认 port + no SSL
connect(host = "example.com", useSSL = true) // named argument improves clarity
在 Kotlin 中,非常建议开发者在进行函数调用的时候,加上参数的名字,这在对于函数的参数列表特别长的时候,有助于提高代码的可读性。
试想一下下面这个 Compose 函数(以下为 Jetpack Compose 中的一个典型复杂函数签名,用于演示命名参数的重要性):
Kotlin
@Composable
fun BasicTextField(
state: TextFieldState,
modifier: Modifier = Modifier,
enabled: Boolean = true,
readOnly: Boolean = false,
inputTransformation: InputTransformation? = null,
textStyle: TextStyle = TextStyle.Default,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
onKeyboardAction: KeyboardActionHandler? = null,
lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
interactionSource: MutableInteractionSource? = null,
cursorBrush: Brush = BasicTextFieldDefaults.CursorBrush,
outputTransformation: OutputTransformation? = null,
decorator: TextFieldDecorator? = null,
scrollState: ScrollState = rememberScrollState(),
)
如果在调用的时候不加参数的名字,那么几乎每次看到这个函数调用,你都要跳转到文档你才能知道具体的功能。
慎用协程的全局作用域
⚠️ 谨慎做法:
Kotlin
GlobalScope.launch { fetchData() }
✅ 推荐做法:
Kotlin
viewModelScope.launch { fetchData() }
通过 viewModelScope
、lifecycleScope
或自定义的 CoroutineScope
使用结构化并发,可确保生命周期管理协程并防止泄漏。
优先使用不可变集合
默认情况下使用 listOf
、mapOf
和 setOf
来实现不可变性。仅在需要进行修改时才切换到 mutableListOf
。
Kotlin
val numbers = listOf(1, 2, 3) // immutable
val mutableNumbers = mutableListOf(1, 2, 3) // modifiable
这个思路和优先使用 val
是一样的。不可变集合可提高并发环境中的安全性,同时也能降低阅读代码的难度。
使用 Result 类型进行错误处理
对比返回 null
或不必要地抛出异常,使用 Kotlin 内置的 Result
类型会让代码更加稳定:
Kotlin
fun parseInt(str: String): Result<Int> {
return runCatching { str.toInt() }
}
val number = parseInt("123").getOrElse { 0 } // 发生错误的回退
这使得错误处理更加明确,同时不会因为忘记 try-catch
导致系统崩溃。
这篇文章讨论了 try-catch
与 runCatching
,能够帮助大家更好的理解 Kotlin 中的异常处理。
⚠️ 注意:在协程中使用
runCatching
时,若捕获到CancellationException
,务必重新抛出,否则协程将无法被正常取消,可能导致"僵尸协程"。
reified 泛型函数
Kotlin 支持 inline
+ reified
泛型,让你在运行时访问类型信息:
Kotlin
inline fun <reified T> Gson.fromJson(json: String): T =
this.fromJson(json, T::class.java)
// Usage
val user: User = gson.fromJson(jsonString)
比手动传递 Class<T>
更简洁、更安全。
有效使用智能类型转换
不要进行显式类型转换,而是依靠 Kotlin 通过 is
检查实现的智能类型转换:
Kotlin
fun printLength(obj: Any) {
if (obj is String) {
println(obj.length) // 智能类型转换为 String
}
}
函数式编程的惯用手法
Kotlin 通过 map
、filter
、flatMap
、reduce
等函数鼓励使用函数式编程:
Kotlin
val evenSquares = (1..10)
.filter { it % 2 == 0 }
.map { it * it }
与手动循环相比,这会使代码更加简洁且富有表现力。
在集合上链式调用多个操作(如 map
+ filter
+ flatMap
)时,会产生中间集合,带来额外内存开销。若操作较复杂或数据量较大,推荐使用 asSequence()
转为惰性序列,以减少中间产物,提升性能:
Kotlin
listOf(1,2,3,4,5).asSequence().map { it * 3 }.filter { it > 2 }.flatMap { (0..it) }.toList()
总结
Kotlin 是一门强大的语言,赋予开发者极大的灵活性。遵循这些高级最佳实践,你编写的代码将更具高性能、安全性与可维护性。
从内联函数到结果类型,再到结构化并发,这些技术将把你的 Kotlin 技能提升到新的高度。