Kotlin 1.7.0 的新特性
Kotlin 1.7.0 的 IDE 支持适用于 IntelliJ IDEA 2021.2、2021.3 和 2022.1。
Kotlin 1.7.0 已发布。它揭示了新 Kotlin/JVM K2 编译器的 Alpha 版本,稳定了语言特性,并为 JVM、JS 和 Native 平台带来了性能改进。
以下是此版本中的主要更新列表:
- 新的 Kotlin K2 编译器现已进入 Alpha 阶段,它提供了显著的性能改进。它仅适用于 JVM,并且包括 kapt 在内的所有编译器插件都无法与其配合使用。
- Gradle 中增量编译的新方法。增量编译现在也支持在依赖的非 Kotlin 模块内部所做的更改,并与 Gradle 兼容。
- 我们已经稳定了选用要求注解、明确的非空类型和构建器推断。
- 现在有了类型参数的下划线运算符。您可以使用它在指定其他类型时自动推断参数类型。
- 此版本允许通过委托将内联类的内联值进行实现。您现在可以创建在大多数情况下不分配内存的轻量级包装器。
您还可以在此视频中找到对更改的简短概述:
适用于 JVM 的新 Kotlin K2 编译器进入 Alpha 阶段
此 Kotlin 版本引入了新 Kotlin K2 编译器的 Alpha 版本。新编译器旨在加速新语言特性的开发,统一 Kotlin 支持的所有平台,带来性能改进,并为编译器扩展提供 API。
我们已经发布了一些关于新编译器及其优势的详细解释:
需要指出的是,新 K2 编译器的 Alpha 版本主要侧重于性能改进,并且它仅适用于 JVM 项目。它不支持 Kotlin/JS、Kotlin/Native 或其他多平台项目,并且包括 kapt 在内的所有编译器插件都无法与其配合使用。
我们的基准测试显示,在我们的内部项目上取得了出色结果:
项目 | 当前 Kotlin 编译器性能 | 新 K2 Kotlin 编译器性能 | 性能提升 |
---|---|---|---|
Kotlin | 2.2 KLOC/秒 | 4.8 KLOC/秒 | ~ x2.2 |
YouTrack | 1.8 KLOC/秒 | 4.2 KLOC/秒 | ~ x2.3 |
IntelliJ IDEA | 1.8 KLOC/秒 | 3.9 KLOC/秒 | ~ x2.2 |
Space | 1.2 KLOC/秒 | 2.8 KLOC/秒 | ~ x2.3 |
TIP
KLOC/秒 的性能数字表示编译器每秒处理的代码行数(以千为单位)。
您可以在您的 JVM 项目上查看性能提升,并将其与旧编译器的结果进行比较。要启用 Kotlin K2 编译器,请使用以下编译器选项:
-Xuse-k2
此外,K2 编译器包含多项 Bug 修复。请注意,此列表中即使是 State: Open 的问题,实际上也已在 K2 中修复。
未来的 Kotlin 版本将改进 K2 编译器的稳定性并提供更多功能,敬请期待!
如果您在使用 Kotlin K2 编译器时遇到任何性能问题,请向我们的问题跟踪器报告。
语言
Kotlin 1.7.0 引入了对委托实现的(implementation by delegation)支持以及类型参数的新下划线运算符。它还稳定了在之前版本中作为预览引入的几项语言特性:
允许通过委托实现内联类的内联值
如果您想为值或类实例创建轻量级包装器,则需要手动实现所有接口方法。委托实现解决了这个问题,但在 1.7.0 之前它不适用于内联类。此限制已被移除,因此您现在可以创建在大多数情况下不分配内存的轻量级包装器。
interface Bar {
fun foo() = "foo"
}
@JvmInline
value class BarWrapper(val bar: Bar): Bar by bar
fun main() {
val bw = BarWrapper(object: Bar {})
println(bw.foo())
}
类型参数的下划线运算符
Kotlin 1.7.0 引入了类型参数的下划线运算符 _
。您可以使用它在指定其他类型时自动推断类型参数:
abstract class SomeClass<T> {
abstract fun execute(): T
}
class SomeImplementation : SomeClass<String>() {
override fun execute(): String = "Test"
}
class OtherImplementation : SomeClass<Int>() {
override fun execute(): Int = 42
}
object Runner {
inline fun <reified S: SomeClass<T>, T> run(): T {
return S::class.java.getDeclaredConstructor().newInstance().execute()
}
}
fun main() {
// T is inferred as String because SomeImplementation derives from SomeClass<String>
val s = Runner.run<SomeImplementation, _>()
assert(s == "Test")
// T is inferred as Int because OtherImplementation derives from SomeClass<Int>
val n = Runner.run<OtherImplementation, _>()
assert(n == 42)
}
NOTE
您可以在变量列表中的任何位置使用下划线运算符来推断类型参数。
稳定的构建器推断
构建器推断是一种特殊的类型推断,在调用泛型构建器函数时非常有用。它有助于编译器使用其 lambda 参数中其他调用的类型信息来推断调用的类型参数。
从 1.7.0 开始,如果常规类型推断无法获取关于类型足够的信息,构建器推断将自动激活,而无需指定 -Xenable-builder-inference
编译器选项,该选项是在 1.6.0 中引入的。
稳定的选用要求
在 1.7.0 之前,选用功能本身需要参数 -opt-in=kotlin.RequiresOptIn
以避免警告。现在不再需要此参数;但是,您仍然可以使用编译器参数 -opt-in
来选用其他注解、一个模块。
稳定的明确非空类型
在 Kotlin 1.7.0 中,明确非空类型已升级为稳定特性。它们在扩展泛型 Java 类和接口时提供了更好的互操作性。
您可以使用新语法 T & Any
在使用点将泛型类型参数标记为明确非空。此语法形式源自交集类型的表示法,现在仅限于 &
左侧具有可空上限的类型参数和右侧的非空 Any
:
fun <T> elvisLike(x: T, y: T & Any): T & Any = x ?: y
fun main() {
// OK
elvisLike<String>("", "").length
// Error: 'null' cannot be a value of a non-null type
elvisLike<String>("", null).length
// OK
elvisLike<String?>(null, "").length
// Error: 'null' cannot be a value of a non-null type
elvisLike<String?>(null, null).length
}
在此 KEEP 中了解有关明确非空类型的更多信息。
Kotlin/JVM
此版本为 Kotlin/JVM 编译器带来了性能改进和新的编译器选项。此外,对函数式接口构造函数的可调用引用已成为稳定特性。请注意,自 1.7.0 起,Kotlin/JVM 编译的默认目标版本为 1.8
。
编译器性能优化
Kotlin 1.7.0 引入了 Kotlin/JVM 编译器的性能改进。根据我们的基准测试,与 Kotlin 1.6.0 相比,编译时间平均减少了 10%。由于字节码后处理的改进,大量使用内联函数的项目,例如使用 kotlinx.html
的项目,将编译得更快。
新的编译器选项:-Xjdk-release
Kotlin 1.7.0 引入了一个新的编译器选项 -Xjdk-release
。此选项类似于 javac 的命令行 --release
选项。-Xjdk-release
选项控制目标字节码版本,并将 classpath 中 JDK 的 API 限制为指定的 Java 版本。例如,即使依赖项中的 JDK 是版本 9 或更高,kotlinc -Xjdk-release=1.8
也不会允许引用 java.lang.Module
。
NOTE
此选项不保证对每个 JDK 分发都有效。
请在此 YouTrack 任务上留下您的反馈。
稳定的函数式接口构造函数的可调用引用
对函数式接口构造函数的可调用引用现在已稳定。了解如何使用可调用引用将接口从具有构造函数迁移到函数式接口。
请在 YouTrack 中报告您发现的任何问题。
移除 JVM 目标版本 1.6
Kotlin/JVM 编译的默认目标版本是 1.8
。1.6
目标已被移除。
请迁移到 JVM 目标 1.8 或更高版本。了解如何更新以下各项的 JVM 目标版本:
Kotlin/Native
Kotlin 1.7.0 包含了对 Objective-C 和 Swift 互操作性的更改,并稳定了在之前版本中引入的特性。它还为新的内存管理器带来了性能改进以及其他更新:
- 新内存管理器的性能改进
- 与 JVM 和 JS IR 后端统一的编译器插件 ABI
- 支持独立 Android 可执行文件
- 与 Swift async/await 的互操作:返回
Void
而非KotlinUnit
- 禁止通过 Objective-C 桥接传递未声明的异常
- 改进的 CocoaPods 集成
- 覆盖 Kotlin/Native 编译器下载 URL
新内存管理器的性能改进
新的内存管理器仍处于 Alpha 阶段,但它正在向稳定发展。此版本为新的内存管理器带来了显著的性能改进,尤其是在垃圾回收 (GC) 方面。特别是,在 1.6.20 中引入的清扫阶段的并发实现现在已默认启用。这有助于减少应用程序因 GC 暂停的时间。新的 GC 调度器更擅长选择 GC 频率,特别是对于更大的堆。
此外,我们还专门优化了调试二进制文件,确保在内存管理器的实现代码中使用了适当的优化级别和链接时优化。这有助于我们将调试二进制文件在基准测试中的执行时间缩短约 30%。
请在您的项目中尝试使用新的内存管理器,看看它是如何工作的,并在 YouTrack 中与我们分享您的反馈。
与 JVM 和 JS IR 后端统一的编译器插件 ABI
从 Kotlin 1.7.0 开始,Kotlin Multiplatform Gradle 插件默认使用 Kotlin/Native 的嵌入式编译器 jar。此功能在 1.6.0 中作为实验性功能宣布,现在已稳定并可供使用。
此改进对库作者来说非常方便,因为它改善了编译器插件的开发体验。在此版本之前,您必须为 Kotlin/Native 提供单独的 Artifact,但现在您可以将相同的编译器插件 Artifact 用于 Native 和其他受支持的平台。
支持独立 Android 可执行文件
Kotlin 1.7.0 提供了对为 Android Native 目标生成标准可执行文件的全面支持。它在 1.6.20 中引入,现在已默认启用。
如果您想回滚到 Kotlin/Native 生成共享库的旧行为,请使用以下设置:
binaryOptions["androidProgramType"] = "nativeActivity"
与 Swift async/await 的互操作:返回 Void 而非 KotlinUnit
Kotlin suspend
函数现在在 Swift 中返回 Void
类型而不是 KotlinUnit
。这是与 Swift 的 async
/await
互操作性改进的结果。此功能在 1.6.20 中引入,此版本默认启用此行为。
您不再需要使用 kotlin.native.binary.unitSuspendFunctionObjCExport=proper
属性来为此类函数返回正确的类型。
禁止通过 Objective-C 桥接传递未声明的异常
当您从 Swift/Objective-C 代码调用 Kotlin 代码(反之亦然),并且此代码抛出异常时,应由发生异常的代码处理,除非您明确允许通过适当的转换在语言之间转发异常(例如,使用 @Throws
注解)。
以前,Kotlin 还有另一个意外行为,即在某些情况下未声明的异常可能会“泄漏”到另一种语言。Kotlin 1.7.0 修复了该问题,现在此类情况会导致程序终止。
因此,例如,如果您在 Kotlin 中有一个 { throw Exception() }
lambda 并从 Swift 调用它,在 Kotlin 1.7.0 中,一旦异常到达 Swift 代码,它就会终止。在之前的 Kotlin 版本中,此类异常可能会泄漏到 Swift 代码。
@Throws
注解将继续像以前一样工作。
改进的 CocoaPods 集成
从 Kotlin 1.7.0 开始,如果您想在项目中集成 CocoaPods,您不再需要安装 cocoapods-generate
插件。
以前,您需要安装 CocoaPods 依赖管理器和 cocoapods-generate
插件才能使用 CocoaPods,例如,在 Kotlin Multiplatform Mobile 项目中处理 iOS 依赖项。
现在设置 CocoaPods 集成更容易了,并且我们解决了 cocoapods-generate
无法在 Ruby 3 及更高版本上安装的问题。现在也支持在 Apple M1 上运行更好的最新 Ruby 版本。
了解如何设置初始 CocoaPods 集成。
覆盖 Kotlin/Native 编译器下载 URL
从 Kotlin 1.7.0 开始,您可以自定义 Kotlin/Native 编译器的下载 URL。当 CI 上禁止外部链接时,这会很有用。
kotlin.native.distribution.baseDownloadUrl=https://example.com
NOTE
下载器会将原生版本和目标操作系统附加到此基本 URL,以确保下载实际的编译器分发。
Kotlin/JS
Kotlin/JS 正在获得对 JS IR 编译器后端的进一步改进,以及其他可以改善您的开发体验的更新:
- 新 IR 后端的性能改进
- 使用 IR 时成员名称的混淆
- IR 后端通过 polyfills 支持旧版浏览器
- 从 js 表达式动态加载 JavaScript 模块
- 为 JavaScript 测试运行器指定环境变量
新 IR 后端的性能改进
此版本包含一些重大更新,应该可以改善您的开发体验:
- Kotlin/JS 的增量编译性能已显著提升。构建 JS 项目所需的时间更少。在许多情况下,增量重建现在应该与旧版后端大致持平。
- Kotlin/JS 最终 Bundle 所需空间更少,因为我们已显著减小了最终 Artifact 的大小。对于一些大型项目,我们测得生产 Bundle 大小与旧版后端相比减少了高达 20%。
- 接口的类型检查已得到数量级的改进。
- Kotlin 生成更高质量的 JS 代码
使用 IR 时成员名称的混淆
Kotlin/JS IR 编译器现在利用其内部关于 Kotlin 类和函数关系的信息,应用更高效的混淆(minification),缩短函数、属性和类的名称。这会缩小生成的捆绑应用程序。
当您在生产模式下构建 Kotlin/JS 应用程序时,此类混淆会自动应用并默认启用。要禁用成员名称混淆,请使用 -Xir-minimized-member-names
编译器标志:
kotlin {
js(IR) {
compilations.all {
compileKotlinTask.kotlinOptions.freeCompilerArgs += listOf("-Xir-minimized-member-names=false")
}
}
}
IR 后端通过 polyfills 支持旧版浏览器
Kotlin/JS 的 IR 编译器后端现在包含了与旧版后端相同的 polyfills。这使得用新编译器编译的代码可以在不支持 Kotlin 标准库所使用的所有 ES2015 方法的旧版浏览器中运行。最终的 bundle 中只包含项目实际使用的 polyfills,这最大限度地减少了它们对 bundle 大小的潜在影响。
使用 IR 编译器时,此功能默认启用,您无需进行配置。
从 js 表达式动态加载 JavaScript 模块
在使用 JavaScript 模块时,大多数应用程序都使用静态导入,其用法已在 JavaScript 模块集成中涵盖。然而,Kotlin/JS 缺少一种在应用程序运行时动态加载 JavaScript 模块的机制。
从 Kotlin 1.7.0 开始,js
块中支持 JavaScript 的 import
语句,允许您在运行时动态地将包引入应用程序:
val myPackage = js("import('my-package')")
为 JavaScript 测试运行器指定环境变量
为了调整 Node.js 包解析或向 Node.js 测试传递外部信息,您现在可以指定 JavaScript 测试运行器使用的环境变量。要定义环境变量,请在构建脚本的 testTask
块内使用带有键值对的 environment()
函数:
kotlin {
js {
nodejs {
testTask {
environment("key", "value")
}
}
}
}
标准库
在 Kotlin 1.7.0 中,标准库收到了一系列更改和改进。它们引入了新功能,稳定了实验性功能,并统一了 Native、JS 和 JVM 平台对命名捕获组的支持:
- min() 和 max() 集合函数返回非空值
- 特定索引处的正则表达式匹配
- 扩展支持以前的语言和 API 版本
- 通过反射访问注解
- 稳定的深度递归函数
- 基于内联类的时间标记用于默认时间源
- Java Optional 的新实验性扩展函数
- JS 和 Native 中对命名捕获组的支持
min() 和 max() 集合函数返回非空值
在 Kotlin 1.4.0 中,我们将 min()
和 max()
集合函数重命名为 minOrNull()
和 maxOrNull()
。这些新名称更好地反映了它们的行为——如果接收集合为空则返回 null。它还有助于使这些函数的行为与整个 Kotlin 集合 API 中使用的命名约定保持一致。
minBy()
、maxBy()
、minWith()
和 maxWith()
也是如此,它们在 Kotlin 1.4.0 中都获得了 *OrNull() 同义词。受此更改影响的旧函数已逐步弃用。
Kotlin 1.7.0 重新引入了原始函数名称,但返回类型为非空。新的 min()
、max()
、minBy()
、maxBy()
、minWith()
和 maxWith()
函数现在严格返回集合元素或抛出异常。
fun main() {
val numbers = listOf<Int>()
println(numbers.maxOrNull()) // "null"
println(numbers.max()) // "Exception in... Collection is empty."
}
特定索引处的正则表达式匹配
Regex.matchAt()
和 Regex.matchesAt()
函数,在 1.5.30 中引入,现在已稳定。它们提供了一种方法来检查正则表达式是否在 String
或 CharSequence
中的特定位置有精确匹配。
matchesAt()
检查匹配并返回布尔结果:
fun main() {
val releaseText = "Kotlin 1.7.0 is on its way!"
// regular expression: one digit, dot, one digit, dot, one or more digits
val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
println(versionRegex.matchesAt(releaseText, 0)) // "false"
println(versionRegex.matchesAt(releaseText, 7)) // "true"
}
matchAt()
如果找到匹配则返回匹配,否则返回 null
:
fun main() {
val releaseText = "Kotlin 1.7.0 is on its way!"
val versionRegex = "\\d[.]\\d[.]\\d+".toRegex()
println(versionRegex.matchAt(releaseText, 0)) // "null"
println(versionRegex.matchAt(releaseText, 7)?.value) // "1.7.0"
}
我们非常感谢您对此 YouTrack 任务的反馈。
扩展支持以前的语言和 API 版本
为了支持库作者开发旨在兼容各种旧版 Kotlin 的库,并应对主要 Kotlin 版本发布频率的增加,我们扩展了对以前语言和 API 版本的支持。
通过 Kotlin 1.7.0,我们支持三个而不是两个以前的语言和 API 版本。这意味着 Kotlin 1.7.0 支持开发面向 Kotlin 1.4.0 及更低版本的库。有关向后兼容性的更多信息,请参阅兼容模式。
通过反射访问注解
KAnnotatedElement.findAnnotations()
扩展函数,首次在 1.6.0 中引入,现在已稳定。此反射函数返回元素上给定类型的所有注解,包括单独应用的注解和重复注解。
@Repeatable
annotation class Tag(val name: String)
@Tag("First Tag")
@Tag("Second Tag")
fun taggedFunction() {
println("I'm a tagged function!")
}
fun main() {
val x = ::taggedFunction
val foo = x as KAnnotatedElement
println(foo.findAnnotations<Tag>()) // [@Tag(name=First Tag), @Tag(name=Second Tag)]
}
稳定的深度递归函数
深度递归函数自 Kotlin 1.4.0 起作为实验性功能提供,现在在 Kotlin 1.7.0 中已稳定。使用 DeepRecursiveFunction
,您可以定义一个函数,该函数将其堆栈保留在堆上,而不是使用实际的调用堆栈。这使您能够运行非常深的递归计算。要调用深度递归函数,请 invoke
它。
在此示例中,深度递归函数用于递归计算二叉树的深度。即使此示例函数递归调用自身 100,000 次,也不会抛出 StackOverflowError
:
class Tree(val left: Tree?, val right: Tree?)
val calculateDepth = DeepRecursiveFunction<Tree?, Int> { t ->
if (t == null) 0 else maxOf(
callRecursive(t.left),
callRecursive(t.right)
) + 1
}
fun main() {
// Generate a tree with a depth of 100_000
val deepTree = generateSequence(Tree(null, null)) { prev ->
Tree(prev, null)
}.take(100_000).last()
println(calculateDepth(deepTree)) // 100000
}
当您的递归深度超过 1000 次调用时,请考虑在代码中使用深度递归函数。
基于内联类的时间标记用于默认时间源
Kotlin 1.7.0 通过将 TimeSource.Monotonic
返回的时间标记更改为内联值类来提高时间测量功能的性能。这意味着调用 markNow()
、elapsedNow()
、measureTime()
和 measureTimedValue()
等函数不会为其 TimeMark
实例分配包装器类。特别是在测量热路径中的一段代码时,这有助于最大限度地减少测量的性能影响:
@OptIn(ExperimentalTime::class)
fun main() {
val mark = TimeSource.Monotonic.markNow() // Returned `TimeMark` is inline class
val elapsedDuration = mark.elapsedNow()
}
NOTE
仅当获取 TimeMark
的时间源在静态上已知为 TimeSource.Monotonic
时,此优化才可用。
Java Optional 的新实验性扩展函数
Kotlin 1.7.0 带来了新的便捷函数,简化了在 Java 中使用 Optional
类。这些新函数可用于在 JVM 上解包和转换可选对象,并有助于使 Java API 的使用更简洁。
getOrNull()
、getOrDefault()
和 getOrElse()
扩展函数允许您获取 Optional
的值(如果存在)。否则,您将分别获得 null
、默认值或函数返回的值:
val presentOptional = Optional.of("I'm here!")
println(presentOptional.getOrNull())
// "I'm here!"
val absentOptional = Optional.empty<String>()
println(absentOptional.getOrNull())
// null
println(absentOptional.getOrDefault("Nobody here!"))
// "Nobody here!"
println(absentOptional.getOrElse {
println("Optional was absent!")
"Default value!"
})
// "Optional was absent!"
// "Default value!"
toList()
、toSet()
和 asSequence()
扩展函数将存在的 Optional
的值转换为列表、集合或序列,否则返回空集合。toCollection()
扩展函数将 Optional
值附加到已存在的目的地集合:
val presentOptional = Optional.of("I'm here!")
val absentOptional = Optional.empty<String>()
println(presentOptional.toList() + "," + absentOptional.toList())
// ["I'm here!"], []
println(presentOptional.toSet() + "," + absentOptional.toSet())
// ["I'm here!"], []
val myCollection = mutableListOf<String>()
absentOptional.toCollection(myCollection)
println(myCollection)
// []
presentOptional.toCollection(myCollection)
println(myCollection)
// ["I'm here!"]
val list = listOf(presentOptional, absentOptional).flatMap { it.asSequence() }
println(list)
// ["I'm here!"]
这些扩展函数在 Kotlin 1.7.0 中作为实验性功能引入。您可以在此 KEEP 中了解有关 Optional
扩展的更多信息。一如既往,我们欢迎您在 Kotlin 问题跟踪器中提供反馈。
JS 和 Native 中对命名捕获组的支持
从 Kotlin 1.7.0 开始,命名捕获组不仅在 JVM 上受支持,而且在 JS 和 Native 平台上也受支持。
要为捕获组命名,请在正则表达式中使用 (?<name>group
) 语法。要获取组匹配的文本,请调用新引入的 MatchGroupCollection.get()
函数并传入组名称。
按名称检索匹配的组值
考虑这个用于匹配城市坐标的示例。要获取正则表达式匹配的组集合,请使用 groups
。比较通过其数字(索引)和通过其名称使用 value
检索组内容:
fun main() {
val regex = "\\b(?<city>[A-Za-z\\s]+),\\s(?<state>[A-Z]{2}):\\s(?<areaCode>[0-9]{3})\\b".toRegex()
val input = "Coordinates: Austin, TX: 123"
val match = regex.find(input)!!
println(match.groups["city"]?.value) // "Austin" — by name
println(match.groups[2]?.value) // "TX" — by number
}
命名反向引用
您现在也可以在反向引用组时使用组名称。反向引用匹配先前由捕获组匹配的相同文本。为此,请在正则表达式中使用 \k<name>
语法:
fun backRef() {
val regex = "(?<title>\\w+), yes \\k<title>".toRegex()
val match = regex.find("Do you copy? Sir, yes Sir!")!!
println(match.value) // "Sir, yes Sir"
println(match.groups["title"]?.value) // "Sir"
}
替换表达式中的命名组
命名组引用可以与替换表达式一起使用。考虑 replace()
函数,该函数用替换表达式替换输入中指定正则表达式的所有出现,以及 replaceFirst()
函数,该函数仅交换第一次匹配。
替换字符串中 ${name}
的出现将替换为与指定名称的捕获组对应的子序列。您可以比较按名称和索引在组引用中的替换:
fun dateReplace() {
val dateRegex = Regex("(?<dd>\\d{2})-(?<mm>\\d{2})-(?<yyyy>\\d{4})")
val input = "Date of birth: 27-04-2022"
println(dateRegex.replace(input, "\${yyyy}-\${mm}-\${dd}")) // "Date of birth: 2022-04-27" — by name
println(dateRegex.replace(input, "\$3-\$2-\$1")) // "Date of birth: 2022-04-27" — by number
}
Gradle
此版本引入了新的构建报告、对 Gradle 插件变体的支持、kapt 的新统计信息以及更多内容:
- 增量编译的新方法
- 用于跟踪编译器性能的新构建报告
- Gradle 和 Android Gradle 插件的最低支持版本更改
- 支持 Gradle 插件变体
- Kotlin Gradle 插件 API 的更新
- sam-with-receiver 插件可通过 plugins API 使用
- 编译任务中的更改
- kapt 中每个注解处理器生成文件的新统计信息
- 弃用 kotlin.compiler.execution.strategy 系统属性
- 移除已弃用的选项、方法和插件
增量编译的新方法
在 Kotlin 1.7.0 中,我们重新设计了跨模块更改的增量编译。现在,增量编译也支持在依赖的非 Kotlin 模块内部所做的更改,并且它与 Gradle 构建缓存兼容。编译避免(compilation avoidance)的支持也得到了改进。
如果您使用构建缓存或经常在非 Kotlin Gradle 模块中进行更改,我们预计您会看到新方法最显著的优势。我们对 Kotlin 项目在 kotlin-gradle-plugin
模块上的测试显示,缓存命中后,更改的改进超过 80%。
要尝试这种新方法,请在您的 gradle.properties
中设置以下选项:
kotlin.incremental.useClasspathSnapshot=true
NOTE
增量编译的新方法目前仅适用于 Gradle 构建系统中的 JVM 后端。
在这篇博客文章中了解增量编译新方法的底层实现。
我们的计划是稳定这项技术并增加对其他后端(例如 JS)和构建系统的支持。如果您在此编译方案中遇到任何问题或异常行为,请在 YouTrack 中报告,我们将不胜感激。谢谢!
Kotlin 团队非常感谢 Ivan Gavrilovic、Hung Nguyen、Cédric Champeau 和其他外部贡献者的帮助。
Kotlin 编译器任务的构建报告
Kotlin 1.7.0 引入了构建报告,有助于跟踪编译器性能。报告包含不同编译阶段的持续时间以及编译无法增量的原因。
构建报告在您想调查编译器任务问题时非常有用,例如:
- 当 Gradle 构建耗时过长,您想了解性能不佳的根本原因时。
- 当同一项目的编译时间不同,有时需要几秒,有时需要几分钟时。
要启用构建报告,请在 gradle.properties
中声明构建报告输出的保存位置:
kotlin.build.report.output=file
以下值(及其组合)可用:
file
将构建报告保存在本地文件中。build_scan
将构建报告保存在 构建扫描 的custom values
部分。NOTE
Gradle Enterprise 插件限制了自定义值的数量和长度。在大型项目中,一些值可能会丢失。
http
使用 HTTP(S) 发布构建报告。POST 方法以 JSON 格式发送指标。数据可能因版本而异。您可以在 Kotlin 仓库中查看发送数据的当前版本。
分析长时间运行编译的构建报告可以帮助您解决两种常见情况:
- 构建不是增量的。分析原因并修复根本问题。
- 构建是增量的,但耗时过长。尝试重组源文件——拆分大文件,将单独的类保存在不同的文件中,重构大型类,在不同的文件中声明顶层函数等等。
在这篇博客文章中了解有关新构建报告的更多信息。
欢迎您在您的基础设施中尝试使用构建报告。如果您有任何反馈、遇到任何问题或想提出改进建议,请随时在我们的问题跟踪器中报告。谢谢!
提高最低支持版本
从 Kotlin 1.7.0 开始,最低支持的 Gradle 版本是 6.7.1。我们不得不提高版本以支持Gradle 插件变体和新的 Gradle API。未来,由于 Gradle 插件变体功能,我们应该不必像现在这样频繁地提高最低支持版本。
此外,最低支持的 Android Gradle 插件版本现在是 3.6.4。
支持 Gradle 插件变体
Gradle 7.0 为 Gradle 插件作者引入了一项新功能——带变体的插件。此功能使添加对新 Gradle 功能的支持变得更容易,同时保持与 7.1 以下 Gradle 版本的兼容性。了解有关 Gradle 中变体选择的更多信息。
通过 Gradle 插件变体,我们可以为不同的 Gradle 版本提供不同的 Kotlin Gradle 插件变体。目标是在 main
变体中支持基本的 Kotlin 编译,这对应于最旧的受支持 Gradle 版本。每个变体都将包含相应发布版本中 Gradle 功能的实现。最新的变体将支持最广泛的 Gradle 功能集。通过这种方法,我们可以扩展对功能有限的旧版 Gradle 的支持。
目前,Kotlin Gradle 插件只有两个变体:
main
用于 Gradle 6.7.1–6.9.3 版本gradle70
用于 Gradle 7.0 及更高版本
在未来的 Kotlin 版本中,我们可能会添加更多。
要检查您的构建使用哪个变体,请启用 --info
日志级别 并在输出中查找以 Using Kotlin Gradle plugin
开头的字符串,例如 Using Kotlin Gradle plugin main variant
。
NOTE
以下是一些 Gradle 中变体选择已知问题的解决方案:
请在此 YouTrack 任务上留下您的反馈。
Kotlin Gradle 插件 API 的更新
Kotlin Gradle 插件 API Artifact 获得了一些改进:
Kotlin/JVM 和 Kotlin/kapt 任务有了新的接口,支持用户可配置的输入。
新增了一个
KotlinBasePlugin
接口,所有 Kotlin 插件都继承自该接口。当您希望在应用任何 Kotlin Gradle 插件(JVM、JS、多平台、Native 及其他平台)时触发某个配置操作时,请使用此接口:kotlinproject.plugins.withType<org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin>() { // Configure your action here }
您可以在此 YouTrack 任务中留下关于
KotlinBasePlugin
的反馈。我们为 Android Gradle 插件在其自身内部配置 Kotlin 编译奠定了基础,这意味着您无需将 Kotlin Android Gradle 插件添加到您的构建中。关注 Android Gradle 插件发布公告以了解新增的支持并进行尝试!
sam-with-receiver 插件可通过 plugins API 使用
sam-with-receiver 编译器插件现在可通过 Gradle plugins DSL 使用:
plugins {
id("org.jetbrains.kotlin.plugin.sam.with.receiver") version "$kotlin_version"
}
编译任务中的更改
此版本中编译任务获得了许多更改:
- Kotlin 编译任务不再继承 Gradle 的
AbstractCompile
任务。它们只继承DefaultTask
。 AbstractCompile
任务有sourceCompatibility
和targetCompatibility
输入。由于AbstractCompile
任务不再被继承,这些输入在 Kotlin 用户脚本中不再可用。SourceTask.stableSources
输入不再可用,您应该使用sources
输入。setSource(...)
方法仍然可用。- 所有编译任务现在都使用
libraries
输入来获取编译所需的库列表。KotlinCompile
任务仍然包含已弃用的 Kotlin 属性classpath
,该属性将在未来的版本中移除。 - 编译任务仍然实现了
PatternFilterable
接口,该接口允许过滤 Kotlin 源。sourceFilesExtensions
输入已被移除,转而使用PatternFilterable
方法。 - 已弃用的
Gradle destinationDir: File
输出已被destinationDirectory: DirectoryProperty
输出替换。 - Kotlin/Native 的
AbstractNativeCompile
任务现在继承了AbstractKotlinCompileTool
基类。这是将 Kotlin/Native 构建工具集成到所有其他工具中的初始步骤。
请在此 YouTrack 任务中留下您的反馈。
kapt 中每个注解处理器生成文件的新统计信息
kotlin-kapt
Gradle 插件已报告每个处理器的性能统计信息。从 Kotlin 1.7.0 开始,它还可以报告每个注解处理器生成的文件数量的统计信息。
这对于跟踪构建中是否存在未使用的注解处理器很有用。您可以使用生成的报告来查找触发不必要的注解处理器的模块,并更新这些模块以防止这种情况发生。
通过两个步骤启用统计信息:
在您的
build.gradle.kts
中将showProcessorStats
标志设置为true
:kotlinkapt { showProcessorStats = true }
在您的
gradle.properties
中将kapt.verbose
Gradle 属性设置为true
:nonekapt.verbose=true
NOTE
您还可以通过命令行选项 verbose
启用详细输出。
统计信息将以 info
级别显示在日志中。您将看到 Annotation processor stats:
行,其后是每个注解处理器执行时间的统计信息。这些行之后将是 Generated files report:
行,其后是每个注解处理器生成的文件数量的统计信息。例如:
[INFO] Annotation processor stats:
[INFO] org.mapstruct.ap.MappingProcessor: total: 290 ms, init: 1 ms, 3 round(s): 289 ms, 0 ms, 0 ms
[INFO] Generated files report:
[INFO] org.mapstruct.ap.MappingProcessor: total sources: 2, sources per round: 2, 0, 0
请在此 YouTrack 任务中留下您的反馈。
弃用 kotlin.compiler.execution.strategy 系统属性
Kotlin 1.6.20 引入了用于定义 Kotlin 编译器执行策略的新属性。在 Kotlin 1.7.0 中,已开始弃用旧的系统属性 kotlin.compiler.execution.strategy
,转而使用新属性。
当您使用 kotlin.compiler.execution.strategy
系统属性时,您将收到警告。此属性将在未来的版本中删除。为了保留旧行为,请将系统属性替换为同名的 Gradle 属性。您可以在 gradle.properties
中执行此操作,例如:
kotlin.compiler.execution.strategy=out-of-process
您还可以使用编译任务属性 compilerExecutionStrategy
。在 Gradle 页面上了解更多信息。
移除已弃用的选项、方法和插件
移除 useExperimentalAnnotation 方法
在 Kotlin 1.7.0 中,我们完成了对 useExperimentalAnnotation
Gradle 方法的弃用周期。请改用 optIn()
来选择启用模块中的 API。
例如,如果您的 Gradle 模块是多平台:
sourceSets {
all {
languageSettings.optIn("org.mylibrary.OptInAnnotation")
}
}
了解 Kotlin 中的选用要求。
移除已弃用的编译器选项
我们已完成了几个编译器选项的弃用周期:
kotlinOptions.jdkHome
编译器选项在 1.5.30 中已弃用,并在此版本中移除。如果 Gradle 构建包含此选项,现在将失败。我们鼓励您使用自 Kotlin 1.5.30 起受支持的 Java 工具链。- 已弃用的
noStdlib
编译器选项也已移除。Gradle 插件使用kotlin.stdlib.default.dependency=true
属性来控制 Kotlin 标准库是否存在。
NOTE
编译器参数 -jdkHome
和 -no-stdlib
仍然可用。
移除已弃用的插件
在 Kotlin 1.4.0 中,kotlin2js
和 kotlin-dce-plugin
插件已弃用,并在此版本中移除。请改用新的 org.jetbrains.kotlin.js
插件来替代 kotlin2js
。当 Kotlin/JS Gradle 插件正确配置时,死代码消除 (DCE) 才会生效。
在 Kotlin 1.6.0 中,我们将 KotlinGradleSubplugin
类的弃用级别更改为 ERROR
。开发人员曾使用此类别来编写编译器插件。在此版本中,此类别已被移除。请改用 KotlinCompilerPluginSupportPlugin
类。
TIP
最佳实践是在整个项目中使用 1.7.0 及更高版本的 Kotlin 插件。
移除已弃用的协程 DSL 选项和属性
我们移除了已弃用的 kotlin.experimental.coroutines
Gradle DSL 选项和 gradle.properties
中使用的 kotlin.coroutines
属性。现在您只需使用 挂起函数 或将 kotlinx.coroutines
依赖项添加到构建脚本中。
在协程指南中了解有关协程的更多信息。
移除工具链扩展方法中的类型转换
在 Kotlin 1.7.0 之前,当使用 Kotlin DSL 配置 Gradle 工具链时,您必须将类型强制转换为 JavaToolchainSpec
类:
kotlin {
jvmToolchain {
(this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(<MAJOR_JDK_VERSION>)
}
}
现在,您可以省略 (this as JavaToolchainSpec)
部分:
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(<MAJOR_JDK_VERSION>)
}
}
迁移到 Kotlin 1.7.0
安装 Kotlin 1.7.0
IntelliJ IDEA 2022.1 和 Android Studio Chipmunk (212) 会自动建议将 Kotlin 插件更新到 1.7.0。
NOTE
对于 IntelliJ IDEA 2022.2,以及 Android Studio Dolphin (213) 或 Android Studio Electric Eel (221),Kotlin 插件 1.7.0 将随即将发布的 IntelliJ IDEA 和 Android Studio 更新一起提供。
新的命令行编译器可在 GitHub 发布页面下载。
迁移现有项目或使用 Kotlin 1.7.0 启动新项目
要将现有项目迁移到 Kotlin 1.7.0,请将 Kotlin 版本更改为
1.7.0
并重新导入您的 Gradle 或 Maven 项目。了解如何更新到 Kotlin 1.7.0。要使用 Kotlin 1.7.0 启动新项目,请更新 Kotlin 插件并从 File | New | Project 运行项目向导。
Kotlin 1.7.0 兼容性指南
Kotlin 1.7.0 是一个功能版本,因此可能会带来与您为早期语言版本编写的代码不兼容的更改。在 Kotlin 1.7.0 兼容性指南中查找此类更改的详细列表。