13.6.6.2 Kotlin 1.8.0 新变化

原文链接: https://kotlinlang.org/docs/whatsnew18.html

13.6.6.2 Kotlin 1.8.0 新变化

阅读 Kotlin 1.8.0 发行说明,了解新的语言特性,以及 Kotlin Multiplatform、JVM、Native、JS 的更新和 Gradle、Maven 的构建工具支持。

发布时间:2022 年 12 月 28 日

Kotlin 1.8.0 已经发布,以下是一些最亮眼的内容:

提示: 有关 Kotlin 发布周期的信息,请参见 Kotlin 发布流程。

IDE 支持

支持 1.8.0 的 Kotlin 插件可用于:

| IDE | 支持的版本 |

| IntelliJ IDEA | 2021.3、2022.1、2022.2 | | Android Studio | Electric Eel (221)、Flamingo (222) |

注意: 在 IntelliJ IDEA 2022.3 中,你无需更新 IDE 插件即可将项目更新到 Kotlin 1.8.0。要在 IntelliJ IDEA 2022.3 中把现有项目迁移到 Kotlin 1.8.0,请将 Kotlin 版本改为 1.8.0 并重新导入你的 Gradle 或 Maven 项目。

Kotlin/JVM

从 1.8.0 版本开始,编译器可以生成字节码版本对应于 JVM 19 的类。新的语言版本还包括:

不生成 TYPE_USE 和 TYPE_PARAMETER 注解目标

如果某个 Kotlin 注解的 Kotlin 目标中包含 TYPE,那么在其 Java 注解目标列表中,该注解会映射到 java.lang.annotation.ElementType.TYPE_USE。TYPE_PARAMETER Kotlin 目标映射到 java.lang.annotation.ElementType.TYPE_PARAMETER Java 目标的情形也是如此。对于 API 级别低于 26 的 Android 客户端来说这是一个问题,因为这些 API 中没有这些目标。

从 Kotlin 1.8.0 开始,你可以使用新的编译器选项 -Xno-new-java-annotation-targets 来避免生成 TYPE_USE 和 TYPE_PARAMETER 注解目标。

用于禁用优化的新编译器选项

Kotlin 1.8.0 新增了 -Xdebug 编译器选项,它会禁用优化以获得更好的调试体验。目前,该选项会禁用协程的“已被优化掉(was optimized out)”特性。将来当我们加入更多优化后,该选项也会禁用它们。

“已被优化掉”特性会在你使用挂起函数时优化变量。然而,调试带有被优化变量的代码很困难,因为你无法看到它们的值。

警告: 切勿在生产环境中使用此选项:通过 -Xdebug 禁用该特性可能导致内存泄漏。

移除旧后端

在 Kotlin 1.5.0 中,我们宣布基于 IR 的后端已成为 Stable。这意味着 Kotlin 1.4.* 的旧后端已被弃用。在 Kotlin 1.8.0 中,我们完全移除了旧后端。相应地,我们也移除了编译器选项 -Xuse-old-backend 和 Gradle 的 useOldBackend 选项。

支持 Lombok 的 @Builder 注解

社区为 Kotlin Lombok:支持生成的构建器(@Builder)这个 YouTrack 问题投了如此多的票,以至于我们不得不支持 @Builder 注解。

我们目前还没有支持 @SuperBuilder 或 @Tolerate 注解的计划,但如果有足够多的人为 @SuperBuilder 和 @Tolerate 问题投票,我们会重新考虑。

了解如何配置 Lombok 编译器插件。

Kotlin/Native

Kotlin 1.8.0 包含对 Objective-C 和 Swift 互操作的更改、对 Xcode 14.1 的支持,以及对 CocoaPods Gradle 插件的改进:

支持 Xcode 14.1

Kotlin/Native 编译器现在支持最新的稳定版 Xcode 14.1。兼容性方面的改进包括以下变更:

  • watchOS 目标有了新的 watchosDeviceArm64 预设,它支持 ARM64 平台上的 Apple watchOS。
  • Kotlin CocoaPods Gradle 插件默认不再为 Apple 框架嵌入 bitcode。
  • 平台库已更新,以反映 Apple 目标的 Objective-C 框架的变更。

改进的 Objective-C/Swift 互操作

为了让 Kotlin 与 Objective-C 和 Swift 更好地互操作,我们新增了三个注解:

  • @ObjCName 允许你在 Swift 或 Objective-C 中指定一个更符合习惯的名称,而无需重命名 Kotlin 声明。

该注解指示 Kotlin 编译器为这个类、属性、参数或函数使用自定义的 Objective-C 和 Swift 名称:

1
2
3
4
5
6
7
8
9
   @ObjCName(swiftName = "MySwiftArray")
   class MyKotlinArray {
       @ObjCName("index")
       fun indexOf(@ObjCName("of") element: String): Int = TODO()
   }

   // 使用 ObjCName 注解的用法
   let array = MySwiftArray()
   let index = array.index(of: "element")

该注解指示 Kotlin 编译器不将函数或属性导出到 Objective-C,从而也不导出到 Swift。这可以让你的 Kotlin 代码对 Objective-C/Swift 更友好。

该注解指示 Kotlin 编译器在生成的 Objective-C API 中把函数或属性标记为 swift_private。这类声明会获得 __ 前缀,从而使它们对 Swift 代码不可见。

你仍然可以在 Swift 代码中使用这些声明来构建对 Swift 友好的 API,但例如 Xcode 的自动补全不会再建议它们。

有关在 Swift 中细化 Objective-C 声明的更多信息,请参见 Apple 官方文档。

注意: 新的注解需要选择启用(opt-in)。

Kotlin 团队非常感谢 Rick Clephas 实现了这些注解。

CocoaPods Gradle 插件默认使用动态框架

从 Kotlin 1.8.0 开始,由 CocoaPods Gradle 插件注册的 Kotlin 框架默认以动态方式链接。之前的静态实现与 Kotlin Gradle 插件的行为不一致。

1
2
3
4
5
6
7
8
kotlin {
    cocoapods {
        framework {
            baseName = "MyFramework"
            isStatic = false // 现在默认为动态
        }
    }
}

如果你的现有项目使用静态链接类型,并且你升级到了 Kotlin 1.8.0(或显式更改了链接类型),你可能会遇到项目执行出错。要解决该问题,请关闭你的 Xcode 项目,并在 Podfile 所在目录中运行 pod install。

更多信息请参见 CocoaPods Gradle 插件 DSL 参考。

Kotlin Multiplatform:新的 Android 源集布局

Kotlin 1.8.0 引入了新的 Android 源集布局,它替换了之前用于目录的命名方案,因为旧方案在多个方面都令人困惑。

来看一个在当前布局中创建两个 androidTest 目录的例子。一个用于 KotlinSourceSets,另一个用于 AndroidSourceSets:

  • 它们具有不同的语义:Kotlin 的 androidTest 属于 unitTest 类型,而 Android 的属于 integrationTest 类型。
  • 它们形成了令人困惑的 SourceDirectories 布局,因为 src/androidTest/kotlin 中有一个 UnitTest,而 src/androidTest/java 中有一个 InstrumentedTest。
  • KotlinSourceSets 和 AndroidSourceSets 对 Gradle 配置使用了相似的命名方案,因此 Kotlin 与 Android 源集中 androidTest 生成的配置是相同的:androidTestImplementation、androidTestApi、androidTestRuntimeOnly 和 androidTestCompileOnly。

为了解决这些以及其他现存问题,我们引入了新的 Android 源集布局。以下是两种布局之间的一些主要区别:

KotlinSourceSet 命名方案

| 当前源集布局 | 新源集布局 |

| targetName + AndroidSourceSet.name | targetName + AndroidVariantType |

{AndroidSourceSet.name} 到 {KotlinSourceSet.name} 的映射如下:

| | 当前源集布局 | 新源集布局 |

| main | androidMain | androidMain | | test | androidTest | androidUnitTest | | androidTest | androidAndroidTest | androidInstrumentedTest |

SourceDirectories

| 当前源集布局 | 新源集布局 |

| 该布局会添加额外的 /kotlin SourceDirectories | src/{AndroidSourceSet.name}/kotlin、src/{KotlinSourceSet.name}/kotlin |

{AndroidSourceSet.name} 到 {SourceDirectories included} 的映射如下:

| | 当前源集布局 | 新源集布局 |

| main | src/androidMain/kotlin, src/main/kotlin, src/main/java | src/androidMain/kotlin, src/main/kotlin, src/main/java | | test | src/androidTest/kotlin, src/test/kotlin, src/test/java | src/androidUnitTest/kotlin, src/test/kotlin, src/test/java | | androidTest | src/androidAndroidTest/kotlin, src/androidTest/java | src/androidInstrumentedTest/kotlin, src/androidTest/java, src/androidTest/kotlin |

AndroidManifest.xml 文件的位置

| 当前源集布局 | 新源集布局 |

| src/{AndroidSourceSet.name}/AndroidManifest.xml | src/{KotlinSourceSet.name}/AndroidManifest.xml |

{AndroidSourceSet.name} 到 {AndroidManifest.xml location} 的映射如下:

| | 当前源集布局 | 新源集布局 |

| main | src/main/AndroidManifest.xml | src/androidMain/AndroidManifest.xml | | debug | src/debug/AndroidManifest.xml | src/androidDebug/AndroidManifest.xml |

Android 插桩测试与通用测试之间的关系

新的 Android 源集布局改变了 Android 插桩测试(在新布局中重命名为 androidInstrumentedTest)与通用测试之间的关系。

之前,androidAndroidTest 与 commonTest 之间存在默认的 dependsOn 关系。在实践中,它意味着:

  • commonTest 中的代码在 androidAndroidTest 中可用。
  • commonTest 中的 expect 声明必须在 androidAndroidTest 中有对应的 actual 实现。
  • 在 commonTest 中声明的测试也会作为 Android 插桩测试运行。

在新的 Android 源集布局中,默认不会添加 dependsOn 关系。如果你更喜欢之前的行为,可以在 build.gradle.kts 文件中手动声明该关系:

1
2
3
4
5
6
7
8
9
kotlin {
    // ...
    sourceSets {
        val commonTest by getting
        val androidInstrumentedTest by getting {
            dependsOn(commonTest)
        }
    }
}

支持 Android 产品风味

之前,Kotlin Gradle 插件会提前创建与带有 debug 和 release 构建类型或自定义风味(如 demo 和 full)的 Android 源集相对应的源集。它通过 val androidDebug by getting { ... } 这样的结构让这些源集可访问。

在新的 Android 源集布局中,这些源集会在 afterEvaluate 阶段创建。这使得此类表达式无效,从而导致类似 org.gradle.api.UnknownDomainObjectException: KotlinSourceSet with name 'androidDebug' not found 的错误。

要绕过这一点,请在 build.gradle.kts 文件中使用新的 invokeWhenCreated() API:

1
2
3
4
5
6
kotlin {
    // ...
    sourceSets.invokeWhenCreated("androidFreeDebug") {
        // ...
    }
}

配置与设置

新布局将在未来的版本中成为默认值。你现在就可以通过以下 Gradle 选项启用它:

kotlin.mpp.androidSourceSetLayoutVersion=2

注意: 新布局需要 Android Gradle 插件 7.0 或更高版本,并且受 Android Studio 2022.3 及更高版本支持。

现在不再建议使用之前的 Android 风格目录。Kotlin 1.8.0 标志着弃用周期的开始,会对当前布局引入警告。你可以通过以下 Gradle 属性抑制该警告:

kotlin.mpp.androidSourceSetLayoutVersion1.nowarn=true

Kotlin/JS

Kotlin 1.8.0 稳定了 JS IR 编译器后端,并为与 JavaScript 相关的 Gradle 构建脚本带来了新特性:

稳定的 JS IR 编译器后端

从这个版本开始,Kotlin/JS 中间表示(基于 IR)编译器后端已进入 Stable。统一所有三个后端的基础设施花了一些时间,但现在它们对 Kotlin 代码使用相同的 IR。

由于 JS IR 编译器后端已稳定,旧的编译器后端从现在起被弃用。

增量编译会随稳定的 JS IR 编译器一起默认启用。

如果你仍在使用旧编译器,请将项目切换到新后端。

用于报告 yarn.lock 已更新的新设置

如果你使用 yarn 包管理器,有三个新的特殊 Gradle 设置可以在 yarn.lock 文件被更新时通知你。当你希望在 CI 构建过程中 yarn.lock 被静默更改时收到通知,就可以使用这些设置。

这三个新的 Gradle 属性是:

  • YarnLockMismatchReport,指定如何报告 yarn.lock 文件的更改。你可以使用以下值之一:
  • FAIL 使相应的 Gradle 任务失败。这是默认值。
  • WARNING 在警告日志中写入更改信息。
  • NONE 禁用报告。
  • reportNewYarnLock,显式报告最近创建的 yarn.lock 文件。默认情况下该选项是禁用的:首次启动时生成新的 yarn.lock 文件是常见做法。你可以使用该选项来确保该文件已提交到你的仓库。
  • yarnLockAutoReplace,每次运行 Gradle 任务时自动替换 yarn.lock。

要使用这些选项,请按如下方式更新构建脚本文件 build.gradle.kts:

1
2
3
4
5
6
7
8
9
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport
import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension

rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) {
    rootProject.the<YarnRootExtension>().yarnLockMismatchReport =
        YarnLockMismatchReport.WARNING // NONE | FAIL
    rootProject.the<YarnRootExtension>().reportNewYarnLock = false // true
    rootProject.the<YarnRootExtension>().yarnLockAutoReplace = false // true
}

通过 Gradle 属性为浏览器添加测试目标

从 Kotlin 1.8.0 开始,你可以直接在 Gradle 属性文件中为不同的浏览器设置测试目标。这样做可以缩小构建脚本文件的大小,因为你不再需要在 build.gradle.kts 中写出所有目标。

你可以使用该属性为所有模块定义浏览器列表,然后在特定模块的构建脚本中添加特定浏览器。

例如,Gradle 属性文件中的下面这行将为所有模块在 Firefox 和 Safari 中运行测试:

kotlin.js.browser.karma.browsers=firefox,safari

请在 GitHub 上查看该属性可用值的完整列表。

Kotlin 团队非常感谢 Martynas Petuška 实现了该特性。

为项目添加 CSS 支持的新方式

此版本提供了一种为项目添加 CSS 支持的新方式。我们预计这会影响很多项目,所以别忘了按如下所述更新你的 Gradle 构建脚本文件。

在 Kotlin 1.8.0 之前,使用 cssSupport.enabled 属性来添加 CSS 支持:

1
2
3
4
5
browser {
    commonWebpackConfig {
        cssSupport.enabled = true
    }
}

现在你应该在 cssSupport {} 块中使用 enabled.set() 方法:

1
2
3
4
5
6
7
browser {
    commonWebpackConfig {
        cssSupport {
            enabled.set(true)
        }
    }
}

Gradle

Kotlin 1.8.0 完整支持 Gradle 7.2 和 7.3 版本。你也可以使用最新 Gradle 版本以内的其他版本,但如果这样做,请记住你可能会遇到弃用警告,或者某些新的 Gradle 特性可能无法工作。

此版本带来了大量变更:

将 Kotlin 编译器选项暴露为 Gradle 惰性属性

为了将可用的 Kotlin 编译器选项暴露为 Gradle 惰性属性,并将它们更好地集成到 Kotlin 任务中,我们做了大量变更:

  • 编译任务有了新的 compilerOptions 输入,它类似现有的 kotlinOptions,但使用 Gradle Properties API 中的 Property 作为返回类型:
1
2
3
4
5
  tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile::class.java) {
      compilerOptions {
          useK2.set(true)
      }
  }
  • Kotlin 工具任务 KotlinJsDce 和 KotlinNativeLink 有了新的 toolOptions 输入,它类似现有的 kotlinOptions 输入。
  • 新的输入带有 @Nested Gradle 注解。输入中的每个属性都有一个相关的 Gradle 注解,例如 @Input 或 @Internal。
  • Kotlin Gradle 插件 API 制品有两个新接口:
  • org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask,它有 compilerOptions 输入和 compileOptions() 方法。所有 Kotlin 编译任务都实现该接口。
  • org.jetbrains.kotlin.gradle.tasks.KotlinToolTask,它有 toolOptions 输入和 toolOptions() 方法。所有 Kotlin 工具任务——KotlinJsDce、KotlinNativeLink 和 KotlinNativeLinkArtifactTask——都实现该接口。
  • 一些 compilerOptions 使用新类型而不是 String 类型:
  • JvmTarget
  • KotlinVersion(用于 apiVersion 和 languageVersion 输入)
  • JsMainFunctionExecutionMode
  • JsModuleKind
  • JsSourceMapEmbedMode

例如,你可以使用 compilerOptions.jvmTarget.set(JvmTarget.JVM_11) 代替 kotlinOptions.jvmTarget = "11"。

kotlinOptions 类型没有变化,它们在内部会被转换为 compilerOptions 类型。

  • Kotlin Gradle 插件 API 与之前的版本二进制兼容。不过,kotlin-gradle-plugin 制品中有一些源码和 ABI 破坏性变更。这些变更大多涉及为某些内部类型添加额外的泛型参数。一个重要变更是 KotlinNativeLink 任务不再继承 AbstractKotlinNativeCompile 任务。
  • KotlinJsCompilerOptions.outputFile 以及相关的 KotlinJsOptions.outputFile 选项已被弃用。请改用 Kotlin2JsCompile.outputFileProperty 任务输入。

注意: Kotlin Gradle 插件仍然会把 KotlinJvmOptions DSL 添加到 Android 扩展中:kotlin android { kotlinOptions { jvmTarget = "11" } } 当 compilerOptions DSL 被添加到模块级别时,这一行为将在此问题的范围内改变。

限制

警告: kotlinOptions 任务输入和 kotlinOptions{...} 任务 DSL 处于支持模式,并将在即将发布的版本中弃用。改进只会针对 compilerOptions 和 toolOptions 进行。

在 kotlinOptions 上调用任何 setter 或 getter 都会委托给 compilerOptions 中的相关属性。这引入了以下限制:

  • compilerOptions 和 kotlinOptions 不能在任务执行阶段更改(请参见下一段中的一个例外)。
  • freeCompilerArgs 返回不可变的 List<String>,这意味着例如 kotlinOptions.freeCompilerArgs.remove("something") 会失败。

一些插件,包括 kotlin-dsl 和启用了 Jetpack Compose 的 Android Gradle 插件(AGP),会尝试在任务执行阶段修改 freeCompilerArgs 属性。我们在 Kotlin 1.8.0 中为它们添加了一个变通方案。该变通方案允许任何构建脚本或插件在执行阶段修改 kotlinOptions.freeCompilerArgs,但会在构建日志中产生警告。要禁用该警告,请使用新的 Gradle 属性 kotlin.options.suppressFreeCompilerArgsModificationWarning=true。Gradle 将为 kotlin-dsl 插件和启用了 Jetpack Compose 的 AGP添加修复。

提升最低支持版本

从 Kotlin 1.8.0 开始,最低支持的 Gradle 版本是 6.8.3,最低支持的 Android Gradle 插件版本是 4.1.3。

请参见我们文档中的 Kotlin Gradle 插件与可用 Gradle 版本的兼容性

能够禁用 Kotlin 守护进程回退策略

有一个新的 Gradle 属性 kotlin.daemon.useFallbackStrategy,其默认值为 true。当值为 false 时,守护进程启动或通信出现问题会导致构建失败。Kotlin 编译任务中还有一个新的 useDaemonFallbackStrategy 属性,如果你同时使用两者,它优先于 Gradle 属性。如果内存不足以运行编译,你可以在日志中看到相关消息。

Kotlin 编译器的回退策略是:如果守护进程以某种方式失败,就在 Kotlin 守护进程之外运行编译。如果 Gradle 守护进程开启,编译器使用“进程内(In process)”策略。如果 Gradle 守护进程关闭,编译器使用“进程外(Out of process)”策略。更多信息请参见编译器执行策略。请注意,静默回退到另一种策略可能消耗大量系统资源,或导致非确定性构建;详情请参见这个 YouTrack 问题。

在传递依赖中使用最新的 kotlin-stdlib 版本

如果你在依赖中显式写明 Kotlin 1.8.0 或更高版本,例如:implementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.0"),那么 Kotlin Gradle 插件会将该 Kotlin 版本用于传递的 kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8 依赖。这样做是为了避免不同 stdlib 版本导致的类重复(进一步了解将 kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8 合并进 kotlin-stdlib)。你可以使用 kotlin.stdlib.jdk.variants.version.alignment Gradle 属性禁用此行为:

kotlin.stdlib.jdk.variants.version.alignment=false

如果你在版本对齐方面遇到问题,可以通过在构建脚本中声明对 kotlin-bom 的平台依赖,借助 Kotlin BOM 对齐所有版本:

1
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))

请在文档中了解其他情况以及我们建议的解决方案。

注意: 即使你的源文件只有 Kotlin 且不使用 Java,本节内容也适用于你的 JVM 项目。

从此次发布开始,对于 Gradle 8.0+(该版本 Gradle 尚未发布)上的项目,kotlin.jvm.target.validation.mode 属性的默认值为 error,在 JVM 目标不兼容时插件会使构建失败。

将默认值从 warning 改为 error 是为顺利迁移到 Gradle 8.0 所做的准备步骤。 我们鼓励你将此属性设置为 error,并配置工具链或手动对齐 JVM 版本。

进一步了解如果不检查目标的兼容性可能会出什么问题。

解析 Kotlin Gradle 插件的传递依赖

在 Kotlin 1.7.0 中,我们引入了对 Gradle 插件变体的支持。由于这些插件变体,一条构建类路径中可能同时存在不同版本的 Kotlin Gradle 插件,它们依赖于某些依赖(通常是 kotlin-gradle-plugin-api)的不同版本。这可能导致解析问题,因此我们以 kotlin-dsl 插件为例,提出以下变通方案。

Gradle 7.6 中的 kotlin-dsl 插件依赖于 org.jetbrains.kotlin.plugin.sam.with.receiver:1.7.10 插件,后者又依赖于 kotlin-gradle-plugin-api:1.7.10。如果你添加 org.jetbrains.kotlin.gradle.jvm:1.8.0 插件,由于版本(1.8.0 和 1.7.10)与变体属性的 org.gradle.plugin.api-version 值不匹配,这个 kotlin-gradle-plugin-api:1.7.10 传递依赖可能导致依赖解析错误。作为变通方案,请添加这个约束来对齐版本。在我们实现已列入计划的 Kotlin Gradle 插件库对齐平台之前,可能都需要这个变通方案:

1
2
3
4
5
dependencies {
    constraints {
        implementation("org.jetbrains.kotlin:kotlin-sam-with-receiver:1.8.0")
    }
}

该约束强制在构建类路径的传递依赖中使用 org.jetbrains.kotlin:kotlin-sam-with-receiver:1.8.0 版本。请在 Gradle 问题跟踪器中了解一个类似的案例。

弃用与移除

在 Kotlin 1.8.0 中,以下属性和方法的弃用周期仍在继续:

标准库

Kotlin 1.8.0:

更新 JVM 编译目标

在 Kotlin 1.8.0 中,标准库(kotlin-stdlib、kotlin-reflect 和 kotlin-script-*)使用 JVM 目标 1.8 编译。此前,标准库使用 JVM 目标 1.6 编译。

Kotlin 1.8.0 不再支持 JVM 目标 1.6 和 1.7。因此,你不再需要在构建脚本中单独声明 kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8,因为这些制品的内容已合并到 kotlin-stdlib 中。

注意: 如果你在构建脚本中显式声明了 kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8 依赖,那么应将它们替换为 kotlin-stdlib。

注意,混用不同版本的 stdlib 制品可能导致类重复或类缺失。为避免这种情况,Kotlin Gradle 插件可以帮助你对齐 stdlib 版本。

cbrt()

cbrt() 函数允许你计算 double 或 float 的实数立方根,现在已进入 Stable。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import kotlin.math.*

fun main() {
    val num = 27
    val negNum = -num

    println("The cube root of ${num.toDouble()} is: " +
            cbrt(num.toDouble()))
    println("The cube root of ${negNum.toDouble()} is: " +
            cbrt(negNum.toDouble()))
}

Java 与 Kotlin 之间的 TimeUnit 转换

kotlin.time 中的 toTimeUnit() 和 toDurationUnit() 函数现在已进入 Stable。这些函数在 Kotlin 1.6.0 中作为实验性特性引入,改进了 Kotlin 与 Java 之间的互操作。现在你可以轻松地在 Java 的 java.util.concurrent.TimeUnit 与 Kotlin 的 kotlin.time.DurationUnit 之间转换。这些函数仅在 JVM 上受支持。

1
2
3
4
5
6
7
import kotlin.time.*

// 供 Java 使用
fun wait(timeout: Long, unit: TimeUnit) {
    val duration: Duration = timeout.toDuration(unit.toDurationUnit())
    ...
}

可比较与可相减的 TimeMark

警告: TimeMark 的新功能是实验性的,要使用它,你需要通过 @OptIn(ExperimentalTime::class) 或 @ExperimentalTime 选择启用。

在 Kotlin 1.8.0 之前,如果你想计算多个 TimeMark 与当前时刻之间的时间差,一次只能在一个 TimeMark 上调用 elapsedNow()。这使得结果难以比较,因为两次 elapsedNow() 函数调用无法在完全相同的时刻执行。

为了解决这个问题,在 Kotlin 1.8.0 中你可以对来自同一时间源的 TimeMark 进行相减和比较。现在你可以创建一个新的 TimeMark 实例来表示当前时刻,并用它减去其他 TimeMark。这样,你从这些计算中得到的结果保证彼此相关。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import kotlin.time.*
fun main() {
    val timeSource = TimeSource.Monotonic
    val mark1 = timeSource.markNow()
    Thread.sleep(500) // 睡眠 0.5 秒
    val mark2 = timeSource.markNow()

    // 1.8.0 之前
    repeat(4) { n ->
        val elapsed1 = mark1.elapsedNow()
        val elapsed2 = mark2.elapsedNow()

        // elapsed1 与 elapsed2 之间的差值可能会变化,具体
        // 取决于两次 elapsedNow() 调用之间经过了多少时间
        println("Measurement 1.${n + 1}: elapsed1=$elapsed1, " +
                "elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
    }
    println()

    // 从 1.8.0 起
    repeat(4) { n ->
        val mark3 = timeSource.markNow()
        val elapsed1 = mark3 - mark1
        val elapsed2 = mark3 - mark2

        // 现在经过的时间是相对于 mark3 计算的,
        // 而 mark3 是一个固定值
        println("Measurement 2.${n + 1}: elapsed1=$elapsed1, " +
                "elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
    }
    // 也可以将时间标记相互比较
    // 这是 true,因为 mark2 捕获的时间晚于 mark1
    println(mark2 > mark1)
}

这个新功能在动画计算中特别有用,因为你可能需要计算或比较代表不同帧的多个 TimeMark 之间的差值。

递归复制或删除目录

警告: 这些针对 java.nio.file.path 的新函数是实验性的。要使用它们,你需要通过 @OptIn(kotlin.io.path.ExperimentalPathApi::class) 或 @kotlin.io.path.ExperimentalPathApi 选择启用。或者,你可以使用编译器选项 -opt-in=kotlin.io.path.ExperimentalPathApi。

我们为 java.nio.file.Path 引入了两个新的扩展函数 copyToRecursively() 和 deleteRecursively(),它们允许你递归地:

  • 将目录及其内容复制到另一个目标位置。
  • 删除目录及其内容。

这些函数在备份过程中非常有用。

错误处理

使用 copyToRecursively() 时,你可以通过重载 onError lambda 函数来定义复制过程中发生异常时应该怎么做:

1
2
3
4
5
sourceRoot.copyToRecursively(destinationRoot, followLinks = false,
    onError = { source, target, exception ->
        logger.logError(exception, "Failed to copy $source to $target")
        OnErrorResult.TERMINATE
    })

当你使用 deleteRecursively() 时,如果删除文件或文件夹时发生异常,那么该文件或文件夹会被跳过。删除完成后,deleteRecursively() 会抛出一个 IOException,其中以被抑制异常的形式包含所有发生的异常。

文件覆盖

如果 copyToRecursively() 发现目标目录中已存在某个文件,就会发生异常。如果你想改为覆盖该文件,请使用带 overwrite 参数的重载并将其设置为 true:

1
2
3
4
5
6
7
fun setUpEnvironment(projectDirectory: Path, fixtureName: String) {
    fixturesRoot.resolve(COMMON_FIXTURE_NAME)
        .copyToRecursively(projectDirectory, followLinks = false)
    fixturesRoot.resolve(fixtureName)
        .copyToRecursively(projectDirectory, followLinks = false,
            overwrite = true) // 修补通用 fixture
}

自定义复制行为

要定义自己的自定义复制逻辑,请使用带有 copyAction 额外参数的重载。通过 copyAction 你可以提供一个 lambda 函数,例如包含你偏好的操作:

1
2
3
4
5
6
7
8
sourceRoot.copyToRecursively(destinationRoot, followLinks = false) { source, target ->
    if (source.name.startsWith(".")) {
        CopyActionResult.SKIP_SUBTREE
    } else {
        source.copyToIgnoringExistingDirectory(target, followLinks = false)
        CopyActionResult.CONTINUE
    }
}

有关这些扩展函数的更多信息,请参见我们的 API 参考。

Java Optional 扩展函数

在 Kotlin 1.7.0中引入的扩展函数现在已进入 Stable。这些函数简化了在 Java 中使用 Optional 类的工作。它们可用于在 JVM 上解包和转换 Optional 对象,并使使用 Java API 更加简洁。更多信息请参见 Kotlin 1.7.0 新变化。

改进的 kotlin-reflect 性能

利用 kotlin-reflect 现在使用 JVM 目标 1.8 编译这一优势,我们将内部缓存机制迁移到了 Java 的 ClassValue。以前我们只缓存 KClass,现在我们还缓存 KType 和 KDeclarationContainer。这些变更在调用 typeOf() 时带来了显著的性能提升。

文档更新

Kotlin 文档有一些值得注意的变更:

改版与新页面

  • Gradle 概览——了解如何使用 Gradle 构建系统配置和构建 Kotlin 项目,以及 Kotlin Gradle 插件中可用的编译器选项、编译和缓存。
  • Java 与 Kotlin 中的可空性——了解 Java 与 Kotlin 处理可能为 null 的变量时的差异。
  • Lincheck 指南——了解如何设置和使用 Lincheck 框架来测试 JVM 上的并发算法。

新增与更新的教程

安装 Kotlin 1.8.0

IntelliJ IDEA 2021.3、2022.1 和 2022.2 会自动建议将 Kotlin 插件更新到 1.8.0 版本。IntelliJ IDEA 2022.3 将在即将到来的小版本更新中内置 1.8.0 版本的 Kotlin 插件。

注意: 要在 IntelliJ IDEA 2022.3 中把现有项目迁移到 Kotlin 1.8.0,请将 Kotlin 版本改为 1.8.0 并重新导入你的 Gradle 或 Maven 项目。

对于 Android Studio Electric Eel (221) 和 Flamingo (222),Kotlin 插件 1.8.0 版本将随即将发布的 Android Studio 更新一起提供。新的命令行编译器可在 GitHub 发布页面下载。

Kotlin 1.8.0 兼容性指南

Kotlin 1.8.0 是一个特性版本,因此可能带来与为早期语言版本编写的代码不兼容的变更。请在 Kotlin 1.8.0 兼容性指南中找到这些变更的详细列表。