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

相关推荐
alexhilton12 小时前
面向开发者的系统设计:像建筑师一样思考
android·kotlin·android jetpack
用户091 天前
Gradle Cache Entries 深度探索
android·java·kotlin
叽哥1 天前
Kotlin学习第 9 课:Kotlin 实战应用:从案例到项目
android·java·kotlin
叽哥2 天前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin
Kapaseker2 天前
每个Kotlin开发者应该掌握的最佳实践,第一趴
kotlin
丑小鸭是白天鹅3 天前
Kotlin协程详细笔记之切线程和挂起函数
开发语言·笔记·kotlin
程序员江同学3 天前
ovCompose + AI 开发跨三端的 Now in Kotlin App
android·kotlin·harmonyos
charlie1145141913 天前
Kotlin 的 apply / with / run 详解
开发语言·kotlin·程序设计·面对对象
柿蒂3 天前
从if-else和switch,聊聊“八股“的作用
android·java·kotlin