跳到主要内容

Kotlin Flow 实战:用数据流管道告别回调地狱

· 阅读需 6 分钟
重庆 Kotlin User Group

你写过这样的代码吗?

// 监听用户输入 → 请求搜索建议 → 过滤结果 → 更新 UI
searchBox.addTextChangedListener { text ->
api.searchSuggestions(text.toString()) { result ->
if (result.isSuccessful) {
val filtered = result.body()?.filter { it.isNotEmpty() }
if (filtered != null) {
runOnUiThread {
adapter.submitList(filtered)
}
}
}
}
}

回调套回调,if 嵌 if——经典的"回调地狱"。更头疼的是,你还得手动处理防抖、取消请求、异常恢复……代码越写越像一团乱麻。

Kotlin 的 Flow(数据流) 就是来终结这种痛苦的。它让你像搭流水线一样,把异步数据一个操作符接一个操作符地串起来,代码从上到下读,逻辑清晰得像白开水。

Kotlin 扩展函数实战:告别 Utils 工具类的优雅姿势

· 阅读需 5 分钟
重庆 Kotlin User Group

你有没有翻过自己半年前写的 Kotlin 代码?我最近就翻了一次,发现了一个 StringUtils.kt 文件,里面躺着一堆这样的函数:

object StringUtils {
fun isEmail(s: String): Boolean {
return s.matches(Regex("^[\\w.-]+@[\\w.-]+\\.\\w+$"))
}

fun isEmpty(s: String?): Boolean {
return s == null || s.isBlank()
}

fun truncate(s: String, maxLen: Int): String {
return if (s.length > maxLen) s.take(maxLen) + "..." else s
}
}

// 调用时
if (StringUtils.isEmail(input)) { ... }
val name = StringUtils.truncate(user.name, 20)

每次调用都要先找到对应的 XxxUtils 对象,再调方法——这浓浓的 Java 味儿,让我意识到:我写了两年 Kotlin,居然一直在写 Java。

Kotlin 作用域函数实战:用 apply/let/run/also 告别临时变量地狱

· 阅读需 6 分钟
重庆 Kotlin User Group

你有没有写过这样的代码?

// 场景:创建一个用户对象并设置各种属性
val user = User()
user.name = "Alice"
user.age = 20
user.email = "alice@example.com"
user.city = "Beijing"

// 或者更糟:为了一个中间结果声明临时变量
val result = computeSomething()
val processed = process(result)
val finalValue = transform(processed)

这种写法能跑,但总让人觉得"不够优雅"。临时变量满天飞,对象初始化代码长得像火车一样。

今天我们来聊聊 Kotlin 的五个作用域函数——apply、let、run、also、with,它们能让你告别临时变量地狱,写出更简洁、更 Kotlin 风格的代码。

Kotlin 2.5 Unicode 代码点 API 实战:用 CodePoint 告别代理对(Surrogate Pair)的噩梦

· 阅读需 6 分钟
重庆 Kotlin User Group

你遇到过这种场景吗?统计一个 emoji 字符串的"字符数",结果数字对不上;或者遍历一个包含罕见汉字的文本,发现一个字被拆成了两个 Char。

来看一段"经典翻车"代码:

val text = "Hello 🌍"
println(text.length) // 7?还是 8?
println(text[6]) // 期望拿到 🌍,实际是一个代理对的高位
println(text[7]) // 这才是代理对的低位

Char 在 Kotlin 中表示一个 UTF-16 编码单元,而像 🌍(U+1F30D)这样的字符超出了 BMP(基本多文种平面),需要两个 Char(即代理对)来表示。直接按 Char 遍历或统计,就会把一个字符拆成两半。

Kotlin 2.5 未使用返回值检查器实战:让编译器帮你揪出那些被静默吞掉的关键结果

· 阅读需 6 分钟
重庆 Kotlin User Group

你有没有遇到过这种场景:代码跑得好好的,测试也通过了,但生产环境就是偶尔出现数据对不上的问题?最后排查了半天,发现罪魁祸首是一个被默默忽略的函数返回值。

来看一段看似无害的代码:

fun processOrder(order: Order) {
val cart = order.cart
// 移除库存不足的商品
cart.items.filter { it.inStock }
// 应用优惠券
cart.total.applyDiscount(order.coupon)
// 保存到数据库
repository.save(cart)
}

问题在哪?filter 不会修改原列表,它返回一个新列表;applyDiscount 如果返回的是新对象而非原地修改,那折扣根本没生效。两行代码的返回值都被静默丢弃了,但编译器一言不发。

Kotlin 2.4.20 when 表达式 invokedynamic 稳定版实战:告别 instanceof 链,拥抱 typeSwitch 字节码

· 阅读需 7 分钟
重庆 Kotlin User Group

你可能不知道,Kotlin 的 when 表达式在 JVM 上编译后,会生成一串冗长的 instanceof 检查。每个分支都是一次类型判断,分支越多,字节码越膨胀。但从 Kotlin 2.4.20 开始,这一切变了——when 表达式正式支持 invokedynamic 编译,默认启用,无需任何额外配置。

来看一个典型的场景:

sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String, val code: Int) : Result()
object Loading : Result()

fun handleResult(result: Result): String = when (result) {
is Success -> "数据: ${result.data}"
is Error -> "错误 ${result.code}: ${result.message}"
is Loading -> "加载中..."
}

Kotlin 2.4 Map fallback 新 API 实战:用 getOrPutIfNull 和 getOrPutIfMissing 告别手动判空

· 阅读需 6 分钟
重庆 Kotlin User Group

在处理配置、缓存或数据映射时,你一定遇到过这样的场景:从 Map 中获取值,如果不存在就提供默认值。Kotlin 的 getOrDefault() 和 getOrPut() 已经很实用了,但它们都有一个盲区——无法区分"键不存在"和"值为 null"。

来看一个真实的例子:

val config = mutableMapOf<String, String?>(
"username" to "alice",
"theme" to null // 用户明确设置了 theme 为 null
)

// 问题:getOrDefault 无法区分"键不存在"和"值为 null"
val theme1 = config.getOrDefault("theme", "light")
println(theme1) // 输出: light ❌ 错误!用户明确设置了 null,应该保留 null

val theme2 = config.getOrDefault("language", "zh-CN")
println(theme2) // 输出: zh-CN ✅ 正确,键不存在时使用默认值

Kotlin 2.4 集合字面量实战:用方括号语法告别 listOf 和 mutableListOf

· 阅读需 5 分钟
重庆 Kotlin User Group

如果你写过 Kotlin,一定对下面这种代码再熟悉不过了:

val names = listOf("Alice", "Bob", "Charlie")
val scores = mutableListOf(95, 87, 72)
val matrix = listOf(listOf(1, 0), listOf(0, 1))

每次创建集合都要敲一遍 listOf() 或 mutableListOf(),嵌套的时候更是括号套娃。Kotlin 2.4 带来了集合字面量(Collection Literals)——一个期待已久的实验性特性,让你可以直接用方括号 [] 创建集合,就像写 JSON 数组一样自然。

Kotlin 2.4.20 集合新 API 实战:用 allDistinct 和 allEqual 告别手动循环判重

· 阅读需 4 分钟
重庆 Kotlin User Group

在日常开发中,你一定写过这样的逻辑:判断一个列表里的元素是否全部相同,或者检查某个字段是否存在重复。过去我们要么手写 for 循环,要么借助 toSet().size == size 这种"凑合能用"的写法。Kotlin 2.4.20 在标准库中正式引入了 allDistinct()、allEqual() 以及它们的 By 变体,把这些常见需求提升为一等公民 API。

Kotlin 非局部 break/continue 实战:告别标签地狱的循环控制新姿势

· 阅读需 6 分钟
重庆 Kotlin User Group

在 Kotlin 中遍历集合并使用高阶函数时,你一定遇到过这样的困境:想在 lambda 里跳过某次迭代或提前终止循环,却发现 continue 和 break 根本无法直接使用——它们只能作用于字面量循环体,而不是 lambda 内部。于是你不得不搬出标签(label),写出 loop@、return@loop、continue@loop 这样令人眼花缭乱的代码。Kotlin 2.2 将非局部 break 与 continue 正式转为稳定特性,让这类跳转变得自然直观。