Kotlin 2.3.20 现已发布,来看看!
大家吼哇,这次轮到 Kotlin 2.3.20 登场啦! 本次更新内容可以在 JetBrains 官方的 What's new in Kotlin 2.3.20 查阅, 我照例挑自己比较感兴趣的一些改动聊聊。
首先总结:语言层面的新玩具不算夸张,但工具链、多平台和互操作体验又被打磨了一圈。 对库作者、折腾构建、和做 KMP 的小伙伴来说,还是蛮不错的。
注意!这次依旧是「我个人」的更新摘要,覆盖不了全部改动;如果你对某个方向特别感兴趣,记得继续深入官方文档喔。
文中示例如无特殊说明均来自或改写自官方日志。
语言特性
这次语言层面的更新不算特别多,不过上来这个我还挺喜欢的:按名称解构。
按名称解构(Name-based destructuring)
以前的解构是纯粹按位置来的,也就是说,只要顺序写错,变量名写得再漂亮也没用:
data class User(val username: String, val email: String)
fun main() {
val user = User("alice", "alice@example.com")
val (email, username) = user
println(email)
// alice
println(username)
// alice@example.com
}
看起来是拿到了 email 和 username,但实际到手上完全是反的。
现在,Kotlin 2.3.20 带来了实验性的按名称解构。显式写法像这样:
fun main() {
val user = User("alice", "alice@example.com")
(val mail = email, val name = username) = user
println(name)
// alice
println(mail)
// alice@example.com
}
简单来说,就是不再依赖 componentN() 的顺序,而是直接按属性名匹配。这个思路我觉得非常合理,
尤其是 data class 字段一长,或者你只是想起个本地变量别名的时候,确实顺手很多。
并且也不一定需要 operator 函数了,任何一个具有属性的普通函数都可以胜任。
这个特性目前还是实验性的,可以通过编译器参数启用:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xname-based-destructuring=only-syntax")
}
}
这个参数有几个模式:
only-syntax:只启用显式的按名称解构语法,不改变现有圆括号解构的行为。name-mismatch:如果位置解构时变量名和属性名对不上,会给 warning。complete:连传统的val (a, b)也会按名称来匹配,而按位置解构则改用方括号写法。
是的,complete 模式下会变成这样:
val [username, email] = user
有一说一,我第一眼看到方括号的时候脑子里闪过的不是“优雅”,而是“咦?这玩意居然真走到这一步了?”。
不过如果官方真打算长期往“默认按名称 解构”这个方向推进,那这套语法倒也说得通。
但这种东西也还是要观望一阵子。如果不管是构建数组/列表,operator fun get(...),索引取数之类的也全都用方括号了,
代码的可读性如何还有待商榷。当然,我对此持乐观态度:我乐于见到更多简化操作的语法糖出现。
上下文参数的重载解析调整
除了新特性,这次还有一个挺值得留意的行为变更:带 context parameters 的声明,不再天然比没有 context parameters 的声明更“具体”了。
以前有些重载组合在带上下文的时候会优先挑中带 context 的版本;
而从 2.3.20 开始,这种“优先照顾上下文版本”的规则没了。结果就是:
如果两个重载只有 context parameters 不同,那原本能编译的地方现在可能直接变成歧义错误。
官方示例大概是这种感觉:
class Logger {
fun info(msg: String) = println("INFO: $msg")
}
fun saveUser(id: Int) {
println("Saving user $id (no logger)")
}
context(logger: Logger)
fun saveUser(id: Int) {
logger.info("Saving user $id")
}
然后你在 context(logger) { saveUser(1) } 里调用时,2.3.20 会认为这俩候选谁都不该无脑赢,进而报歧义。
这类更新对普通业务代码的体感可能不一定很强,但对库作者、DSL 作者或者喜欢玩上下文参数的小伙伴来说,算是一个需要留意的兼容性点。
顺便一提,kotlin.context 相关重载数量也从 22 个缩到 6 个了,代码补全和解析压力理论上会更轻一些。
以明确额错误代替未知行为的歧义,在我看来不失为一种良好的选择,我认可这个变化。
标准库
Map.Entry.copy()
终于能放心把 Entry 拿出来用了咯~ 标准库这次给 Map.Entry 加了一个实验性的 copy() 扩展函数,用来直接拷贝一个不可变副本出来。
以前如果你从 map.entries 里拿出一些 Entry,然后再去修改原 map,这些 Entry 往往就不太可靠了。
现在可以先 copy() 成一个不可变副本,再继续干活:
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val map = mutableMapOf(1 to 1, 2 to 2, 3 to 3, 4 to 4)
val toRemove = map.entries
.filter { it.key % 2 == 0 }
.map { it.copy() }
map.entries.removeAll(toRemove)
println("map = $map")
// map = {1=1, 3=3}
}
它也是实验性的,使用时需要 @OptIn(ExperimentalStdlibApi::class),
或者加编译器参数 -opt-in=kotlin.ExperimentalStdlibApi。
编译器插件
这次编译器插件相关有两个点,我觉得都值得一提。