一、概述
The Kotlin 2.1.0 release is here! Here are the main highlights:
Kotlin 2.1.0发布了,主要亮点如下:
- New language features in preview: Guard conditions in when with a subject, non-local break and continue, and multi-dollar string interpolation.
- K2 compiler updates: More flexibility around compiler checks and improvements to the kapt implementation.
- Kotlin Multiplatform: Introduced basic support for Swift export, stable Gradle DSL for compiler options, and more.
- Kotlin/Native: Improved support for iosArm64 and other updates.
- Kotlin/Wasm: Multiple updates, including support for incremental compilation.
- Gradle support: Improved compatibility with newer versions of Gradle and the Android Gradle plugin, along with updates to the Kotlin Gradle plugin API.
- Documentation: Significant improvements to the Kotlin documentation.
二、 when 表达式主条件里使用守卫条件
提醒:该特性在 2.1.0 版本还是 preview 阶段,需要启用此特性:
在单个when表达式中,可以组合带有或不带有保护条件的分支。带有保护条件的分支中的代码只有在主条件和保护条件都为真时才运行。如果主条件不匹配,则不计算保护条件。此外,保护条件支持else if。
要在项目中启用保护条件,请在命令行中使用以下编译器选项:
kotlinc -Xwhen-guards main.kt
或者在app的build.gradle.kts文件里添加compilerOptions {}
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}
从2.1.0开始,可以在when表达式或带有主题的语句中使用保护条件。
保护条件允许您为when表达式的分支包含多个条件,从而使复杂的控制流更加明确和简洁,并使代码结构扁平化。
要在分支中包含保护条件,将其放在主条件之后,用if分隔:
kt_210.kt
sealed interface Animal {
data class Cat(val mouseHunter: Boolean, val name: String) : Animal {
fun feedCat() {
println("执行了 feedCat 方法 name:$name ")
}
}
data class Dog(val breed: String, val name: String) : Animal {
fun feedDog() {
println("执行了 feedDog 方法 name:$name")
}
}
}
/*
when 表达式里 主条件 后面可以使用 守卫条件
*/
fun feedAnimal(animal: Animal) {
when (animal) {
// 仅有主条件分支
is Animal.Dog -> animal.feedDog()
// 有主条件和守卫条件 分支
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// 条件不匹配分支
else -> println("走了 条件不匹配 逻辑")
}
}
fun main() {
feedAnimal(Animal.Dog("大型犬", "德牧"))
feedAnimal(Animal.Cat(true, "狸花猫1"))
feedAnimal(Animal.Cat(false, "狸花猫2"))
}
执行效果图
三、非局部中断并继续
要在项目中使用该特性,请在命令行中使用以下编译器选项:
kotlinc -Xnon-local-break-continue main.kt
或者在app的build.gradle.kts文件里添加compilerOptions {}
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnon-local-break-continue")
}
}
Kotlin 2.1.0增加了另一个期待已久的预览功能,即使用非本地中断和继续的能力。此特性扩展了可以在内联函数范围内使用的工具集,并减少了项目中的样板代码。
以前,您只能使用非本地返回。现在,Kotlin还支持非局部的break和continue跳转表达式。这意味着你可以将它们应用于作为参数传递给包含循环的内联函数的lambdas中:
kt_210.kt
fun processList(elements: List<ItemBean>): Boolean {
for (element in elements) {
val totalCount = element.getRealTotalCount() ?: run {
println("${element.name} totalCount is null , continuing...")
continue
}
if (totalCount == 0) return true // If variable is zero, return true
}
return false
}
data class ItemBean(val totalCount: Int?, val name: String?) {
fun getRealTotalCount(): Int? {
if (totalCount == null){
return null
}
return totalCount - 6
}
}
fun main() {
val result = processList(listOf(ItemBean(20, "西兰花"), ItemBean(null, "胡萝卜"), ItemBean(8, "西芹"),
ItemBean(2, "洋葱"))
)
println("执行 processList 方法返回结果 result = $result")
}
执行效果图
四、多个$符号字符串插值
要在项目中使用该特性,请在命令行中使用以下编译器选项:
kotlinc -Xmulti-dollar-interpolation main.kt
或者在app的build.gradle.kts文件里添加compilerOptions {}
Kotlin 2.1.0引入了对多美元字符串插值的支持,改进了在字符串字面量中处理美元符号($)的方式。此特性在需要多个美元符号的上下文中很有帮助,例如模板引擎、JSON模式或其他数据格式。
Kotlin中的字符串插值使用单个美元符号。但是,在字符串中使用美元符号(这在财务数据和模板系统中很常见)需要使用诸如${‘$’}之类的变通方法。启用多美元插值功能后,您可以配置触发插值的美元符号数量,将更少的美元符号视为字符串字面值。
下面是一个如何使用$生成带有占位符的JSON模式多行字符串的示例:
val KClass<*>.jsonSchema : String
get() = $$"""
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"$dynamicAnchor": "meta"
"title": "$${simpleName ?: qualifiedName ?: "unknown"}",
"type": "object"
}
"""
在这个例子中,初始的$$意味着你需要两个美元符号($$)来触发插值。它可以防止$schema、$id和$dynamicAnchor被解释为插值标记。
这种方法在处理使用美元符号作为占位符语法的系统时特别有用。
五、支持要求选择加入来扩展api
Kotlin 2.1.0引入了@SubclassOptInRequired注释,它允许库作者在用户实现实验接口或扩展实验类之前要求显式的选择。
当库API足够稳定,可以使用,但可能会随着新的抽象函数而发展,使其不稳定,无法继承时,此特性可能很有用。
要将opt-in要求添加到API元素中,可以使用@SubclassOptInRequired注释和对注释类的引用:
@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Interfaces in this library are experimental"
)
annotation class UnstableApi()
@SubclassOptInRequired(UnstableApi::class)
interface CoreLibraryApi
在这个例子中,CoreLibraryApi接口要求用户在实现它之前选择加入。用户可以这样选择:
@OptIn(UnstableApi::class)
interface MyImplementation: CoreLibraryApi
注意:当你使用@SubclassOptInRequired注释来要求选择加入时,这个要求不会传播到任何内部类或嵌套类。
六、改进了泛型函数的重载解析
以前,如果您对一个函数进行了多次重载,其中一些函数具有泛型类型的值参数,而其他函数具有相同位置的函数类型,则解析行为有时可能不一致。
这会导致不同的行为,这取决于您的重载是成员函数还是扩展函数。例如:
class KeyValueStore<K, V> {
fun store(key: K, value: V) {} // 1
fun store(key: K, lazyValue: () -> V) {} // 2
}
fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, value: V) {} // 1
fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, lazyValue: () -> V) {} // 2
fun test(kvs: KeyValueStore<String, Int>) {
// Member functions
kvs.store("", 1) // Resolves to 1
kvs.store("") { 1 } // Resolves to 2
// Extension functions
kvs.storeExtension("", 1) // Resolves to 1
kvs.storeExtension("") { 1 } // Doesn't resolve
}
在本例中,KeyValueStore类对store()函数有两个重载,其中一个重载具有泛型类型K和V的函数参数,另一个重载具有返回泛型类型V的lambda函数。类似地,扩展函数有两个重载:storeExtension()。
当调用store()函数时,无论是否使用lambda函数,编译器都会成功解析正确的重载。但是,当使用lambda函数调用扩展函数storeExtension()时,编译器没有解析正确的重载,因为它错误地认为两个重载都适用。
为了解决这个问题,我们引入了一种新的启发式方法,以便当泛型类型的函数形参不能根据来自不同实参的信息接受lambda函数时,编译器可以丢弃可能的重载。这一更改使成员函数和扩展函数的行为保持一致,并且在Kotlin 2.1.0中默认启用。
七、改进了带密封类的表达式的耗尽性检查
在以前的Kotlin版本中,当类型参数的表达式具有密封上界时,编译器需要一个else分支,即使在密封类层次结构中的所有情况都被覆盖时也是如此。这种行为在Kotlin 2.1.0中得到了解决和改进,使耗尽性检查更强大,并允许您删除冗余的else分支,使when表达式更干净、更直观。
下面是一个展示这种变化的例子:
sealed class Result
object Error: Result()
class Success(val value: String): Result()
fun <T : Result> render(result: T) = when (result) {
Error -> "Error!"
is Success -> result.value
// Requires no else branch
}