在 Part 1,介绍了 Kotlin 的基础最佳实践,例如不可变性、空安全、数据类、协程和密封类。
现在,更进一步。在这部分中,探讨有助于为生产级应用编写更简洁、更安全、更高效代码的高级 Kotlin 最佳实践。
对于高阶函数,优先使用内联函数
高阶函数可能会带来额外开销。当把函数作为参数传递时,使用 inline 关键字以避免不必要的对象创建并提升性能。
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println("Took ${System.currentTimeMillis() - start} ms")
}
不仅如此,这种技巧对于日志打印也尤为重要:
inline fun logD(tag: String, content: () -> String) {
if (Debug) {
println("$tag: ${content()}")
}
}
logD("TAG") { "用户点击了 $something" }
传统的日志打印方式,无论日志是否最终输出,都会产生字符串拼接的开销。如果采用内联高阶函数的方式,不仅省去了拼接字符串的开销,还能省去高阶函数的额外开销。
使用类型别名(typealias)以提高可读性
如果你的函数签名变得冗长且重复,可使用类型别名(typealias)来简化它们。
typealias Callback = (Boolean, String?) -> Unit // 简化冗长的 lambda 表达式
fun loginUser(username: String, password: String, callback: Callback) {
// ...
}
typealias UserOrderMap = Map<String, OrderList> // 简化冗长的泛型集合
这使你的 API 更简洁、更易读。
适当使用 const 和 @JvmField
- 对于编译时常量,使用
const val。
- 在 companion object 中,当 Java 互操作需要直接访问字段以避免 getter 开销时,使用
@JvmField。
class ApiConfig {
companion object {
const val TIMEOUT = 30_000
@JvmField val TAG = "ApiLogger"
}
}
class Blog {
@JvmField
val id: Int = 123
}
在 Java 中访问时:
System.out.println(ApiConfig.TIMEOUT);
Blog blog = new Blog();
System.out.println(blog.id);
而如果没有加 @JvmField 的话,在 Java 中访问是这样的:
System.out.println(blog.getId());
利用默认参数与具名参数
与其创建多个重载,不如使用默认参数。
fun connect(host: String, port: Int = 8080, useSSL: Boolean = false) { ... }
connect("example.com") // 默认端口 + 不启用 SSL
connect("example.com", useSSL = true) // 具名参数有助于提高可读性
在 Kotlin 中,非常建议开发者在进行函数调用的时候,加上参数的名字,这在对于函数的参数列表特别长的时候,有助于提高代码的可读性。
试想一下下面这个 Compose 函数(以下为 Jetpack Compose 中的一个典型复杂函数签名,用于演示命名参数的重要性):
@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(),
)
如果在调用的时候不加参数的名字,那么几乎每次看到这个函数调用,你都要跳转到文档你才能知道具体的功能。
避免在协程中滥用 GlobalScope
❗ 谨慎做法:
GlobalScope.launch { fetchData() }
✅ 推荐做法:
viewModelScope.launch { fetchData() }
通过 viewModelScope、lifecycleScope 或自定义 CoroutineScope 使用结构化并发。这可确保协程进行生命周期感知的管理并防止泄漏。
优先使用不可变集合
默认使用 listOf、mapOf 和 setOf 实现不可变性;只有在需要修改时才改用 mutableListOf。
val numbers = listOf(1, 2, 3) // 不可变
val mutableNumbers = mutableListOf(1, 2, 3) // 可修改
这个思路和优先使用 val 是一样的。不可变集合在并发环境中提高了安全性,同时也能降低阅读代码的难度。
使用 Result 类型进行错误处理
与其返回 null 或不必要地抛出异常,不如使用 Kotlin 内置的 Result。
fun parseInt(str: String): Result<Int> {
return runCatching { str.toInt() }
}
val number = parseInt("123")
.getOrElse { 0 } // 发生错误的回退
这使得错误处理变得显式且可组合,同时不会因为忘记 try-catch 导致系统崩溃。
编写具化(reified)泛型函数
Kotlin 允许将 inline 与 reified 泛型结合使用,从而在运行时访问类型信息。
Copy
inline fun <reified T> Gson.fromJson(json: String): T =
this.fromJson(json, T::class.java)
val user: User = gson.fromJson(jsonString)
比手动传递 Class<T> 更简洁、更安全。
有效使用智能类型转换
与其显式地进行类型转换,不如通过 is 检查依赖 Kotlin 的智能类型转换。
fun printLength(obj: Any) {
if (obj is String) {
println(obj.length) // 智能类型转换为 String
}
}
应用函数式编程惯用法
Kotlin 通过 map、filter、flatMap、reduce 等操作鼓励函数式编程。
val evenSquares = (1..10)
.filter { it % 2 == 0 }
.map { it * it }
与手动循环相比,这会使代码更简洁、更具表达力。
在集合上链式调用多个操作(如 map + filter + flatMap)时,会产生中间集合,带来额外内存开销。若操作较复杂或数据量较大,推荐使用 asSequence() 转为惰性序列,以减少中间产物,提升性能:
listOf(1,2,3,4,5).asSequence().map { it * 3 }.filter { it > 2 }.flatMap { (0..it) }.toList()
总结
Kotlin 是一门强大的语言,为开发者提供了很高的灵活性。遵循这些高级最佳实践,开发者将能编写出高性能、安全且适用于生产环境的代码。
从内联函数到 Result 类型与结构化并发,这些技术将把你的 Kotlin 技能提升到新的层次。
参考
在 Part 1,介绍了 Kotlin 的基础最佳实践,例如不可变性、空安全、数据类、协程和密封类。
现在,更进一步。在这部分中,探讨有助于为生产级应用编写
更简洁、更安全、更高效代码的高级 Kotlin 最佳实践。对于高阶函数,优先使用内联函数
高阶函数可能会带来额外开销。当把函数作为参数传递时,使用
inline关键字以避免不必要的对象创建并提升性能。不仅如此,这种技巧对于日志打印也尤为重要:
传统的日志打印方式,无论日志是否最终输出,都会产生字符串拼接的开销。如果采用内联高阶函数的方式,不仅省去了拼接字符串的开销,还能省去高阶函数的额外开销。
使用类型别名(typealias)以提高可读性
如果你的函数签名变得冗长且重复,可使用
类型别名(typealias)来简化它们。这使你的 API
更简洁、更易读。适当使用
const和@JvmFieldconst val。@JvmField。在 Java 中访问时:
而如果没有加
@JvmField的话,在 Java 中访问是这样的:利用默认参数与具名参数
与其创建多个重载,不如使用默认参数。
在 Kotlin 中,非常建议开发者在进行函数调用的时候,加上参数的名字,这在对于函数的参数列表特别长的时候,有助于提高代码的可读性。
试想一下下面这个
Compose函数(以下为Jetpack Compose中的一个典型复杂函数签名,用于演示命名参数的重要性):如果在调用的时候不加参数的名字,那么几乎每次看到这个函数调用,你都要跳转到文档你才能知道具体的功能。
避免在协程中滥用 GlobalScope
❗ 谨慎做法:
GlobalScope.launch { fetchData() }✅ 推荐做法:
viewModelScope.launch { fetchData() }通过
viewModelScope、lifecycleScope或自定义CoroutineScope使用结构化并发。这可确保协程进行生命周期感知的管理并防止泄漏。优先使用不可变集合
默认使用
listOf、mapOf和setOf实现不可变性;只有在需要修改时才改用mutableListOf。这个思路和优先使用
val是一样的。不可变集合在并发环境中提高了安全性,同时也能降低阅读代码的难度。使用 Result 类型进行错误处理
与其返回
null或不必要地抛出异常,不如使用 Kotlin 内置的Result。这使得错误处理变得显式且可组合,同时不会因为忘记
try-catch导致系统崩溃。编写具化(reified)泛型函数
Kotlin 允许将
inline与reified泛型结合使用,从而在运行时访问类型信息。比手动传递
Class<T>更简洁、更安全。有效使用智能类型转换
与其显式地进行类型转换,不如通过
is检查依赖 Kotlin 的智能类型转换。应用函数式编程惯用法
Kotlin 通过
map、filter、flatMap、reduce等操作鼓励函数式编程。与手动循环相比,这会使
代码更简洁、更具表达力。在集合上链式调用多个操作(如
map+filter+flatMap)时,会产生中间集合,带来额外内存开销。若操作较复杂或数据量较大,推荐使用asSequence()转为惰性序列,以减少中间产物,提升性能:总结
Kotlin 是一门强大的语言,为开发者提供了很高的灵活性。遵循这些高级最佳实践,开发者将能编写出高性能、安全且适用于生产环境的代码。
从内联函数到 Result 类型与结构化并发,这些技术将把你的 Kotlin 技能提升到新的层次。
参考