以下为:https://kotlinlang.org/docs/whatsnew20.html 文章译文
Kotlin 2.0.0 版本已经发布,新的Kotlin K2编译器是稳定的!此外,以下是一些其他亮点:
- 新的Compose编译器Gradle插件
- 使用invokedynamic生成lambda函数
- kotlinx-metadata-jvm库现在很稳定
- 在苹果平台上使用路标监控Kotlin/Native中的GC性能
- 使用Objective-C方法解决Kotlin/Native中的冲突
- 支持Kotlin/Wasm中的命名导出
- 在Kotlin/Wasm中使用@JsExport的函数中支持无符号原始类型
- 默认使用Binaryen优化生产构建
- 用于多平台项目中编译器选项的新Gradle DSL
- 稳定替换枚举类值通用函数
- 稳定的AutoCloseable接口
IDE 支持
支持Kotlin 2.0.0的Kotlin插件捆绑在最新的IntelliJ IDEA和Android Studio中。您无需在IDE中更新Kotlin插件。您只需在构建脚本中将Kotlin版本更改为Kotlin 2.0.0。
- 有关IntelliJ IDEA支持Kotlin K2编译器的详细信息,请参阅IDE中的支持。
- 有关IntelliJ IDEA支持Kotlin的更多详细信息,请参阅Kotlin版本。
Kotlin K2编译器
通往K2编译器的道路是漫长的,但现在JetBrains团队已准备好宣布其稳定。在Kotlin 2.0.0中,默认使用新的Kotlin K2编译器,它对所有目标平台都是稳定的:JVM、Native、Wasm和JS。新的编译器带来了重大的性能改进,加快了新语言功能的开发,统一了Kotlin支持的所有平台,并为多平台项目提供了更好的架构。
JetBrains团队通过从选定的用户和内部项目中成功编译1000万行代码,确保了新编译器的质量。18,000名开发人员和80,000个项目参与了稳定过程,在他们的项目中尝试了新的K2编译器,并报告了他们发现的任何问题。
为了帮助使迁移到新编译器的过程尽可能顺利,我们创建了一个K2编译器迁移指南。本指南解释了编译器的许多好处,突出了您可能遇到的任何更改,并描述了如何在必要时回滚到以前的版本。
我们在一篇博客文章中探讨了K2编译器在不同项目中的性能。如果您想查看有关K2编译器如何运行的真实数据,并找到有关如何从您自己的项目中收集性能基准的说明,请查看它。
当前的K2编译器限制
在Gradle项目中启用K2具有某些限制,在以下情况下,这些限制可能会影响使用低于8.3的Gradle版本的项目:
- 从
buildSrc
编译源代码。 - 编译包含的构建中的Gradle插件。
- 编译其他Gradle插件,如果它们用于Gradle版本低于8.3的项目。
- 构建Gradle插件依赖项。
如果您遇到上述任何问题,您可以采取以下步骤来解决它们:
- 设置
buildSrc
、任何Gradle插件及其依赖项的语言版本:
kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}
如果您为特定任务配置语言和API版本,这些值将覆盖
compilerOptions
扩展设置的值。在这种情况下,语言和API版本不应高于1.9。
- 将项目中的Gradle版本更新到8.3或更高版本。
智能转换改进
在特定情况下,Kotlin编译器可以自动将对象转换为类型,从而为您节省了自己显式转换对象的麻烦。这被称为智能铸造。Kotlin K2编译器现在在比以前更多的场景中执行智能转换。
在Kotlin 2.0.0中,我们在以下领域对智能转换进行了改进:
- 局部变量和进一步的范围
- 使用逻辑或运算符进行类型检查
- 内联功能
- 具有函数类型的属性
- 异常处理
- 增量和减量运营商
局部变量和进一步的范围
以前,如果变量在 if
条件内被评估为非 null
,则该变量将是智能转换的。然后,有关该变量的信息将在 if
代码块的范围内进一步共享。
但是,如果您在 if
条件 外部 声明了变量,则在 if
条件中将没有关于该变量的信息,因此它不能是智能转换的。在 when
表达式和 while
循环中也可以看到这种行为。
从Kotlin 2.0.0开始,如果您在 if
、 when
或 while
条件下使用变量之前声明变量,那么编译器收集的有关变量的任何信息都可以在相应的块中访问,以便进行智能转换。
当您想将布尔条件提取到变量中时,这可能很有用。然后,您可以给变量一个有意义的名称,这将提高您的代码可读性,并有可能在稍后的代码中重用该变量。例如:
class Cat {
fun purr() {
println("Purr purr")
}
}
fun petAnimal(animal: Any) {
val isCat = animal is Cat
if (isCat) {
// 在 Kotlin 2.0.0 中,编译器可以访问有关 Cat 的信息,因此它知道animal被智能转换为 Cat 类型。
// 因此,可以调用 purr() 函数。
// 在 Kotlin 1.9.20 中,编译器不知道智能强制转换,因此调用 purr() 函数会出错。
animal.purr()
}
}
fun main() {
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
使用逻辑或运算符进行类型检查
在Kotlin 2.0.0中,如果您将对象的类型检查与 or
运算符( ||
)相结合,则智能转换到它们最接近的通用超类型。在这一变化之前,智能演员总是对 Any
类型进行。
在这种情况下,您仍然需要手动检查对象类型,然后才能访问其任何属性或调用其函数。例如:
interface Status {
fun signal() {
}
}
interface Ok : Status
interface Postponed : Status
interface Declined : Status
fun signalCheck(signalStatus: Any) {
if (signalStatus is Postponed || signalStatus is Declined) {
// signalStatus是智能投射到常见的超类型状态
signalStatus.signal()
// 在Kotlin 2.0.0之前,signalStatus被智能强制转换为Any类型,因此调用signal()函数会触发一个Unresolved引用错误。
// signal()函数只能在另一次类型检查后才能成功调用:
// check(signalStatus is Status)
// signalStatus.signal()
}
}
常见的超型是并集类型的近似值。Kotlin不支持联合类型。
内联方法
在Kotlin 2.0.0中,K2编译器以不同的方式处理内联函数,允许它与其他编译器分析相结合,确定智能转换是否安全。
具体来说,内联函数现在被视为具有隐式 callsInPlace
合同。这意味着传递给内联函数的任何lambda函数都会被调用。由于lambda函数是就地调用的,编译器知道lambda函数不能泄露对其函数主体中包含的任何变量的引用。
编译器使用这些知识以及其他编译器分析来决定智能转换任何捕获的变量是否安全。例如:
interface Processor {
fun process()
}
inline fun inlineAction(f: () -> Unit) = f()
fun nextProcessor(): Processor? = null
fun runProcessor(): Processor? {
var processor: Processor? = null
inlineAction {
// 在Kotlin 2.0.0中,编译器知道processor是一个局部变量,而inlineAction()是一个内联函数,
// 所以对处理器的引用不能泄露。因此,它是安全的
if (processor != null) {
// 编译器知道处理器不是空的,所以不需要安全调用
processor.process()
// 在Kotlin 1.9.20中,你必须执行一个安全调用:
// processor?.process()
}
processor = nextProcessor()
}
return processor
}
具有函数类型的属性
在以前版本的Kotlin中,有一个错误,这意味着具有函数类型的类属性不是智能转换的。我们在Kotlin 2.0.0和K2编译器中修复了此行为。例如:
class Holder(val provider: (() -> Unit)?) {
fun process() {
// 在Kotlin 2.0.0中,如果provider不为空,那么provider将被智能强制转换
if (provider != null) {
// 编译器知道provider不为空
provider()
// 在1.9.20中,编译器不知道provider不是null,所以它触发了一个错误:
// Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
}
}
}
如果您重载 invoke
运算符,此更改也适用。例如:
interface Provider {
operator fun invoke()
}
interface Processor : () -> String
class Holder(val provider: Provider?, val processor: Processor?) {
fun process() {
if (provider != null) {
provider()
// 在1.9.20中,编译器触发了一个错误:
// Reference has a nullable type 'Provider?' use explicit '?.invoke()' to make a function-like call instead
}
}
}
异常处理
在Kotlin 2.0.0中,我们改进了异常处理,以便智能转换信息可以传递到 catch
和 finally
代码块。此更改使您的代码更安全,因为编译器会跟踪您的对象是否具有可空的类型。例如:
fun testString() {
var stringInput: String? = null
// stringInput是智能转换到String类型
stringInput = ""
try {
// 编译器知道stringInput不是空的
println(stringInput.length)
// 0
// 编译器拒绝stringInput之前的智能强制转换信息。现在stringInput有String?类型。
stringInput = null
// 触发异常
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// 在Kotlin 2.0.0中,编译器知道stringInput可以为空,因此stringInput保持为空。
println(stringInput?.length)
// null
// 在Kotlin 1.9.20中,编译器说不需要安全调用,但这是不正确的。
}
}
increment 和 decrement 操作符
在Kotlin 2.0.0之前,编译器不明白在使用增量或递减运算符后,对象的类型可以更改。由于编译器无法准确跟踪对象类型,您的代码可能会导致未解决的引用错误。在Kotlin 2.0.0中,这已被修复:
interface Rho {
operator fun inc(): Sigma = TODO()
}
interface Sigma : Rho {
fun sigma() = Unit
}
interface Tau {
fun tau() = Unit
}
fun main(input: Rho) {
var unknownObject: Rho = input
// 检查未知对象是否从Tau接口继承
if (unknownObject is Tau) {
// 使用来自接口 Rho 的重载 inc() 运算符,
// 智能将未知对象的类型投射到 Sigma。
++unknownObject
// 在Kotlin 2.0.0中,编译器知道 unknownObject 具有 Sigma 类型,因此可以成功调用 sigma() 函数。
unknownObject.sigma()
// 在Kotlin 1.9.20中,编译器认为 unknownObject 的类型是 Tau,因此不允许调用 sigma() 函数。
// 在Kotlin 2.0.0中,编译器知道 unknownObject 的类型是 Sigma,因此不允许调用 tau() 函数。
unknownObject.tau()
// 未解决的引用 'tau'
// 在Kotlin 1.9.20中,编译器错误地认为 unknownObject 的类型是 Tau, tau() 函数可以成功调用。
}
}
Kotlin多平台改进
在Kotlin 2.0.0中,我们在以下领域对与Kotlin Multiplatform相关的K2编译器进行了改进:
- 编译过程中 公共 和 平台源 的分离
- 预期和实际申报的不同可见性级别
编译过程中公共和平台源的分离
以前,Kotlin编译器的设计使其无法在编译时将公共源集和平台源集分开。因此,公共代码可以访问平台代码,这导致平台之间的不同行为。此外,一些编译器设置和常见代码的依赖项用于泄漏到平台代码中。
在Kotlin 2.0.0中,我们对新Kotlin K2编译器的实现包括重新设计编译方案,以确保在公共源集和平台源集之间严格分离。当您使用预期和实际功能时,这种变化最为明显。以前,通用代码中的函数调用可以解析为平台代码中的函数。例如:
通用代码
fun foo(x: Any) = println("common foo")
fun exampleFunction() {
foo