每个Kotlin开发者应该掌握的最佳实践,第二趴

上一篇文章中,我们介绍了 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() }

通过 viewModelScopelifecycleScope 或自定义的 CoroutineScope 使用结构化并发,可确保生命周期管理协程并防止泄漏。

优先使用不可变集合

默认情况下使用 listOfmapOfsetOf 来实现不可变性。仅在需要进行修改时才切换到 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-catchrunCatching,能够帮助大家更好的理解 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 通过 mapfilterflatMapreduce 等函数鼓励使用函数式编程:

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 技能提升到新的高度。

相关推荐
alexhilton4 小时前
Compose中的ContentScale:终极可视化指南
android·kotlin·android jetpack
jzlhll1235 小时前
kotlin Flow first() last()总结
开发语言·前端·kotlin
符哥20089 小时前
充电桩 WiFi 局域网配网(Android/Kotlin)流程、指令及实例说明文档
android·开发语言·kotlin
大傻^13 小时前
SpringAI2.0 Null Safety 实战:JSpecify 注解体系与 Kotlin 互操作
android·开发语言·人工智能·kotlin·springai
jzlhll12317 小时前
Kotlin Mutex vs Java ReentrantLock vs synchronized
java·开发语言·kotlin
Kapaseker18 小时前
一杯 Kotlin 美式品味 object 声明
android·kotlin
俩个逗号。。18 小时前
Kotlin 扩展函数详解
开发语言·kotlin
su1ka1112 天前
Kotlin(3)基本语法
kotlin
su1ka1112 天前
Kotlin(4)面向对象
kotlin
鹧鸪晏2 天前
搞懂 kotlin 泛型 out 和 in 关键字
android·kotlin