Kotlin 最佳实践

FreeGuideOnline 最新 2026-07-15

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 优先使用 firstOrNullfind 等明确的查询方法

避免 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 组件中使用 viewModelScopelifecycleScope

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-catchCoroutineExceptionHandler,注意协程内异常的传播规则。

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 避免不必要的对象创建

  • 尽量使用基本类型数组 IntArrayLongArray 而非 Array<Int> 以避免装箱开销。
  • 字符串拼接过长时考虑 buildString { }

7.2 合理使用 lazylateinit 延迟初始化

  • 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
  • 逗号后空格
  • 函数或类的大括号不要另起一行(类头除外)
  • 尽可能使用 whentry-catch 等作为表达式

8.2 导入有意义的别名

当类名冲突或过长时,使用 import ... as ... 提高可读性。

import com.example.ancientdata.StrangeLongName as DataModel

8.3 统一的错误处理与日志

使用 requirecheck 进行函数前置条件检查;?: 提供默认值或抛出自定义异常。

fun prepare(value: String) {
    require(value.isNotBlank()) { "value must not be blank" }
}