13.6.9.2 Kotlin 1.5.20 的新变化

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

13.6.9.2 Kotlin 1.5.20 的新变化

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

发布日期:2021 年 6 月 24 日

Kotlin 1.5.20 修复了 1.5.0 新特性中发现的问题,还包含各种工具改进。

你可以在发布博客文章和这个视频中查看变更概览:

视频:Kotlin 1.5.20

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

Kotlin/JVM

Kotlin 1.5.20 在 JVM 平台上带来了以下更新:

通过 invokedynamic 进行字符串拼接

Kotlin 1.5.20 在面向 JVM 9+ 的目标时会把字符串拼接编译为动态调用(invokedynamic),从而与现代化 Java 版本保持一致。更准确地说,它使用 StringConcatFactory.makeConcatWithConstants() 进行字符串拼接。

要切换回以前版本使用的、通过 StringBuilder.append() 进行拼接的方式,请添加编译器选项 -Xstring-concat=inline。

在 Gradle、Maven 和命令行编译器中了解如何添加编译器选项。

支持 JSpecify 可空性注解

Kotlin 编译器可以读取各种可空性注解,以便把可空性信息从 Java 传递到 Kotlin。1.5.20 版本引入了对 JSpecify 项目的支持,其中包含一套标准统一的 Java 可空性注解。

借助 JSpecify,你可以提供更详细的可空性信息,帮助 Kotlin 在与 Java 互操作时保持空安全。你可以为声明、包或模块作用域设置默认可空性,指定参数化可空性等等。JSpecify 用户指南中有更多细节。

下面是一个示例,展示 Kotlin 如何处理 JSpecify 注解:

1
2
3
4
5
6
7
8
// JavaClass.java
import org.jspecify.nullness.*;

@NullMarked
public class JavaClass {
  public String notNullableString() { return ""; }
  public @Nullable String nullableString() { return ""; }
}
1
2
3
4
5
// Test.kt
fun kotlinFun() = with(JavaClass()) {
  notNullableString().length // 可以
  nullableString().length // 警告:接收者可空性不匹配
}

在 1.5.20 中,根据 JSpecify 提供的可空性信息检测到的所有可空性不匹配都会以警告形式报告。在使用 JSpecify 时,请使用 -Xjspecify-annotations=strict 和 -Xtype-enhancement-improvements-strict-mode 编译器选项启用严格模式(以错误形式报告)。请注意,JSpecify 项目正在积极开发中,其 API 和实现可能随时发生重大变化。

进一步了解空安全和平台类型。

支持在同时包含 Kotlin 和 Java 代码的模块中调用 Java 的 Lombok 生成方法

警告: Lombok 编译器插件是实验性的。它可能随时被移除或更改。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。

Kotlin 1.5.20 引入了实验性的 Lombok 编译器插件。该插件让你可以在同时包含 Kotlin 和 Java 代码的模块中生成并使用 Java 的 Lombok 声明。Lombok 注解只在 Java 源码中生效,如果你在 Kotlin 代码中使用它们,会被忽略。

该插件支持以下注解:

  • @Getter、@Setter
  • @NoArgsConstructor、@RequiredArgsConstructor 和 @AllArgsConstructor
  • @Data
  • @With
  • @Value

我们仍在继续开发该插件。要了解当前的详细状态,请访问 Lombok 编译器插件的 README。

目前我们没有支持 @Builder 注解的计划。不过,如果你在 YouTrack 上为 @Builder 投票,我们会考虑它。

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

Kotlin/Native

Kotlin/Native 1.5.20 提供了新特性的预览和工具改进:

选择启用把 KDoc 注释导出到生成的 Objective-C 头文件

警告: 把 KDoc 注释导出到生成的 Objective-C 头文件的能力是实验性的。它可能随时被移除或更改。需要选择启用(详见下文),并且你应仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。

现在你可以让 Kotlin/Native 编译器把 Kotlin 代码中的文档注释(KDoc)导出到由它生成的 Objective-C framework 中,使 framework 的使用者可以看到它们。

例如,以下带 KDoc 的 Kotlin 代码:

1
2
3
4
5
/**
 * 打印各参数之和。
 * 正确处理了和不适合 32 位整数的情况。
 */
fun printSum(a: Int, b: Int) = println(a.toLong() + b)

会生成以下 Objective-C 头文件:

1
2
3
4
5
/**
 * 打印各参数之和。
 * 正确处理了和不适合 32 位整数的情况。
 */
+ (void)printSumA:(int32_t)a b:(int32_t)b __attribute__((swift_name("printSum(a:b:)")));

这在 Swift 中同样可以很好地工作。

要试用把 KDoc 注释导出到 Objective-C 头文件的能力,请使用 -Xexport-kdoc 编译器选项。在你希望导出注释的 Gradle 项目的 build.gradle(.kts) 中添加以下几行:

Kotlin

1
2
3
4
5
kotlin {
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        compilations.get("main").kotlinOptions.freeCompilerArgs += "-Xexport-kdoc"
    }
}

Groovy

1
2
3
4
5
kotlin {
    targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
        compilations.get("main").kotlinOptions.freeCompilerArgs += "-Xexport-kdoc"
    }
}

如果你能通过这个 YouTrack 议题与我们分享反馈,我们将非常感激。

编译器缺陷修复

Kotlin/Native 编译器在 1.5.20 中获得了多项缺陷修复。你可以在更新日志中找到完整列表。

其中有一项影响兼容性的重要缺陷修复:在此前版本中,包含不正确 UTF 代理对的字符串常量会在编译期间丢失其值。现在这类值会被保留。应用开发者可以放心更新到 1.5.20 —— 不会有任何东西被破坏。不过,用 1.5.20 编译的库与更早的编译器版本不兼容。细节请参阅这个 YouTrack 议题。

改进同一数组内 Array.copyInto() 的性能

我们改进了 Array.copyInto() 在源和目标为同一个数组时的行为。由于针对这一用例的内存管理优化,这类操作现在快最多 20 倍(取决于所复制的对象数量)。

Kotlin/JS

在 1.5.20 中,我们发布了一份指南,帮助你把自己的项目迁移到 Kotlin/JS 新的基于 IR 的后端。

JS IR 后端的迁移指南

新的 JS IR 后端迁移指南列出了你在迁移期间可能遇到的问题,并提供了解决方案。如果你发现指南中没有覆盖的问题,请把它们报告到我们的问题跟踪器。

Gradle

Kotlin 1.5.20 引入了以下可以改善 Gradle 使用体验的特性:

在 kapt 中缓存注解处理器的类加载器

警告: kapt 中注解处理器类加载器的缓存是实验性的。它可能随时被移除或更改。请仅将其用于评估目的。我们非常欢迎你在 YouTrack 上提供反馈。

现在有一个新的实验性特性,可以在 kapt 中缓存注解处理器的类加载器。该特性可以提升连续多次 Gradle 运行时 kapt 的速度。

要启用该特性,请在 gradle.properties 文件中使用以下属性:

# positive value will enable caching
# use the same value as the number of modules that use kapt
kapt.classloaders.cache.size=5

# disable for caching to work
kapt.include.compile.classpath=false

进一步了解 kapt。

弃用 kotlin.parallel.tasks.in.project 构建属性

从本次发布开始,Kotlin 的并行编译由 Gradle 并行执行标志 --parallel控制。使用该标志,Gradle 会并发执行任务,从而提升编译任务的速度并更高效地利用资源。

你不再需要使用 kotlin.parallel.tasks.in.project 属性。该属性已被弃用,并将在下一个大版本中移除。

标准库

Kotlin 1.5.20 修改了若干处理字符的函数的平台特有实现,从而实现了跨平台的统一:

在 Kotlin/Native 和 Kotlin/JS 中支持 Char.digitToInt() 的所有 Unicode 数字

Char.digitToInt() 返回该字符所表示的十进制数字的数值。在 1.5.20 之前,该函数只在 Kotlin/JVM 上支持所有 Unicode 数字字符:Native 和 JS 平台上的实现只支持 ASCII 数字。

从现在开始,无论是 Kotlin/Native 还是 Kotlin/JS,你都可以对任何 Unicode 数字字符调用 Char.digitToInt() 并得到它的数值表示。

1
2
3
4
fun main() {
    val ten = '\u0661'.digitToInt() + '\u0039'.digitToInt() // 阿拉伯-印度数字一 + 数字九
    println(ten)
}

统一各平台上 Char.isLowerCase()/isUpperCase() 的实现

函数 Char.isUpperCase() 和 Char.isLowerCase() 根据字符的大小写返回布尔值。对于 Kotlin/JVM,其实现会同时检查 General_Category 和 Other_Uppercase/Other_Lowercase 这两个 Unicode 属性。

在 1.5.20 之前,其他平台的实现行为不同,只考虑通用类别。在 1.5.20 中,各平台的实现得到统一,会使用这两个属性来判断字符大小写:

1
2
3
4
5
fun main() {
    val latinCapitalA = 'A' // 具有 "Lu" 通用类别
    val circledLatinCapitalA = 'Ⓐ' // 具有 "Other_Uppercase" 属性
    println(latinCapitalA.isUpperCase() && circledLatinCapitalA.isUpperCase())
}