Kotlin 1.5.30 版中的新功能
Kotlin 1.5.30 带来语言更新, 包括功能变更的预览, 平台支持与工具方面的大量改进, 以及新的标准库函数.
下面是主要的功能改进:
语言功能, 包括封闭式(sealed)
when
语句(实验性功能), 明确要求使用者同意的功能(Opt-in Requirement)的使用方法变更, 以及其他更新支持 Apple Silicon 平台的原生(Native)开发
Kotlin/JS IR 后端升级为 Beta 版
Gradle plugin 使用体验改进
关于功能变更的简短介绍, 也可以参见 发布公告, 以及下面的视频:
语言功能
Kotlin 1.5.30 提供了未来的语言功能变更的预览, 并带来了要求使用者同意的功能(Opt-in Requirement)和类型推断的改进:
针对封闭类或布尔值的穷尽式(exhaustive) when 语句
一个 穷尽式 when
语句包含对应于所有可能的类型或值的分支, 对于某些类型, 再加上 else
分支. 也就是说, 它覆盖私有可能的情况.
我们计划很快禁用非穷尽的 when
语句, 使得 when
语句的动作与 when
表达式一致. 为了保证平滑移植, 你可以配置编译器, 对封闭类或布尔值的非穷尽 when
语句报告警告. 在 Kotlin 1.6 中默认会出现这些警告, 将来会变为错误.
要在 Kotlin 1.5.30 中启用这个功能, 请使用语言版本 1.6
. 你也可以启用 渐进模式, 将警告变为错误.
挂起函数用作超类型
Kotlin 1.5.30 提供了一个功能预览, 可以将一个 suspend
函数类型用作一个超类型, 但存在一些限制.
使用 -language-version 1.6
编译器选项来启用这个功能:
这个功能存在以下限制:
在超类型中, 你不能混合使用一个通常的函数类型与一个的
suspend
函数类型. 这是由于suspend
函数类型在 JVM 后端中的实现细节造成的. 它表达为一个通常的函数类型加上一个标记接口. 由于这个标记接口, 无法区分哪个父接口是挂起函数, 哪个是通常函数.你不能使用多个
suspend
函数作为超类型. 如果存在类型检查, 你也不能使用多个通常的函数作为超类型.
隐含使用实验性 API 时要求使用者同意
库的作者可以将一个试验性 API 标记为 要求使用者同意, 用来提醒使用者这个功能处于试验性状态. 当使用这个 API 时, 编译器会报告一个警告或错误, 并要求 明确同意使用 来消除这些警告或错误.
在 Kotlin 1.5.30 中, 对于签名中存在试验性类型的任何声明, 编译器都认为它们是试验性的. 也就是说, 即使对一个试验性 API 的隐含使用, 它也要求使用者同意. 比如, 如果函数的返回类型标记为试验性 API 元素, 那么使用这个函数会也要求你明确同意, 即使函数声明本身明确没有标记为需要使用者同意.
详情请参见 明确要求使用者同意.
要求使用者同意注解对不同目标的使用方式的变更
在不同的 注解目标 上的要求使用者同意的注解的使用和声明, Kotlin 1.5.30 现在使用新的规则. 对于编译期无法处理的使用场景, 编译器现在会报告错误. 在 Kotlin 1.5.30 中:
在使用端, 禁止对局部变量和值参数标注要求使用者同意的注解.
对 override, 只有当它的原始声明也进行了标注, 才允许进行标注.
禁止对后端域和 get 方法标注. 你可以改为标注属性.
在要求使用者同意的注解的声明端, 禁止将注解目标设置为
TYPE
和TYPE_PARAMETER
.
详情请参见 明确要求使用者同意的功能.
对递归泛型类型的类型推断的改进
在 Kotlin 和 Java 中, 你可以定义一个递归泛型类型, 在它的类型参数中引用它自身. 在 Kotlin 1.5.30 中, 如果一个类型参数是递归泛型, 那么 Kotlin 编译器可以只根据对应的类型参数的上界(Upper Bound)推断出这个类型参数. 因此, 可以使用递归泛型类型, 创建出 Java 中经常用来创建构建器 API 的很多模式.
你可以传递 -Xself-upper-bound-inference
或 -language-version 1.6
编译器选项来启用这个改进. 关于新支持的使用场景的其他示例, 请参见 这个 YouTrack ticket.
去掉了构建器推断的限制
构建器推断一种特殊的类型推断, 可以根据一个调用的 Lambda 参数之内的其他调用的类型信息, 来推断这个调用的类型参数. 当调用泛型构建器函数时, 这个功能可以很有用, 比如 buildList()
或 sequence()
: buildList { add("string") }
.
在这样一个 Lambda 参数内部, 以前曾经存在一个限制, 不能使用构建器推断功能尝试推断的类型信息. 因此你只能指定这个类型信息, 而不能通过推断得到它. 比如, 在 buildList()
的 Lambda 参数之内, 除非明确指定类型参数, 否则你不能调用 get()
.
Kotlin 1.5.30 使用 -Xunrestricted-builder-inference
编译器选项去掉了这个限制. 添加这个选项, 可以在泛型构建器函数的 Lambda 参数之内, 启用以前被禁止的调用:
你还可以通过 -language-version 1.6
编译器选项启用这个功能.
Kotlin/JVM
在 Kotlin 1.5.30 版中, Kotlin/JVM 新增了以下功能:
关于 JVM 平台上的 Kotlin Gradle plugin 的更新, 请参见 Gradle 小节.
创建注解类的实例
在 Kotlin 1.5.30 中, 现在你可以在任何代码中调用 注解类 的构造器, 来获得一个实例. 这个功能能够用于 Java 中相同的使用场景, 可以实现一个注解接口.
使用 -language-version 1.6
编译器选项来启用这个功能. 注意, 注解类现有的所有限制都继续存在, 比如不能定义 非 val
参数, 或与次级构造器(secondary constructor)不同的成员.
关于创建注解类的实例, 更多详情请参见 这个 KEEP.
可否为 null(Nullability) 注解的支持配置的改进
Kotlin 编译器可以读取多种类型的 可否为 null(Nullability) 注解, 来从 Java 代码得到可否为 null 信息. 这个信息使得它能够报告 Kotlin 中调用 Java 代码时的可否为 null 不匹配的错误.
在 Kotlin 1.5.30 中, 你可以指定编译器是否根据指定的可否为 null 注解类型的信息来报告可否为 null 不匹配的错误. 只需要使用编译器选项 -Xnullability-annotations=@<package-name>:<report-level>
. 在参数中, 指定可否为 null 注解的全限定包名称, 以及以下报告级别中的一个:
ignore
: 忽略可否为 null 不匹配warn
: 报告为警告strict
: 报告为错误.
详情请参见 支持的可否为 null 注解列表 以及它们的全限定包名称.
下面是一个示例, 演示如何对新支持的 RxJava 3 可否为 null 注解启用错误报告: -Xnullability-annotations=@io.reactivex.rxjava3.annotations:strict
. 注意, 所有这些可否为 null 不匹配, 默认设置为警告.
Kotlin/Native
Kotlin/Native 包含以下变更和改进:
支持 Apple Silicon
Kotlin 1.5.30 引入了对 Apple Silicon 的原生支持.
在以前的版本中, Kotlin/Native 编译器和工具需要 Rosetta 翻译环境 才能在 Apple Silicon 主机上工作. 在 Kotlin 1.5.30 中, 不再需要翻译环境 – 编译器和工具可以在 Apple Silicon 硬件上运行, 不需要任何额外的操作.
我们还引入了新的编译目标, 可以使 Kotlin 代码在 Apple Silicon 上直接运行:
macosArm64
iosSimulatorArm64
watchosSimulatorArm64
tvosSimulatorArm64
这些编译目标可以用于 Intel 和 Apple Silicon 的主机. 所有既有的编译目标也可以在 Apple Silicon 主机上使用.
注意, 在 1.5.30 中, 我们只在 kotlin-multiplatform
Gradle plugin 中提供对 Apple Silicon 编译目标的基本的支持. 具体来说, 在 ios
, tvos
, 和 watchos
编译目标简写(target shortcut) 中没有包含新的模拟器编译目标. 我们会继续改进这些新的编译目标的使用体验.
CocoaPods Gradle plugin 的 Kotlin DSL 的改进
Kotlin/Native Framework 的新参数
Kotlin 1.5.30 带来了 CocoaPods Gradle plugin DSL 关于 Kotlin/Native Framework 的改进. 除了 Framework 名称之外, 你还可以在 Pod 配置中指定其他参数:
指定 Framework 的动态或静态版本
明确启用导出依赖项
启用 Bitcode 内嵌
要使用新的 DSL, 请将你的项目更新到 Kotlin 1.5.30, 并在你的 build.gradle(.kts)
文件的 cocoapods
节中指定参数:
对 Xcode 配置支持自定义名称
Kotlin CocoaPods Gradle plugin 在 Xcode 构建配置中支持自定义名称. 如果你在 Xcode 中为构建配置使用了特殊的名称, 比如 Staging
, 这个功能也可以帮助你.
要指定一个自定义名称, 请在你的 build.gradle(.kts)
文件的 cocoapods
节中使用 xcodeConfigurationToNativeBuildType
参数:
这个参数不会出现在 Podspec 文件中. 当 Xcode 运行 Gradle 构建过程时, Kotlin CocoaPods Gradle plugin 会选择必要的原生构建类型.
与 Swift 5.5 async/await 的交互 (实验性功能)
过去我们曾经 在 1.4.0 中添加了从 Objective-C 和 Swift 中调用 Kotlin 挂起函数的功能, 现在我们删除这个功能, 改为使用新的 Swift 5.5 功能 – 使用 async
和 await
修饰符的并发功能.
对于返回类型可为 null 的挂起函数, Kotlin/Native 编译器现在会在生成的 Objective-C 头文件中, 输出 _Nullable_result
属性. 因此在 Swift 中可以将这些函数作为 async
函数来调用, 并且得到正确的可否为 null 结果.
注意, 这个功能是实验性功能, 未来可能由于 Kotlin 和 Swift 的变化而受到影响. 目前来说, 我们提供这个功能的一个预览版, 带有一些限制, 我们期待得到你的意见反馈. 请在 这个 YouTrack issue 中查看这个功能目前的状态, 并留下你的反馈意见.
对象和伴随对象到 Swift/Objective-C 的映射的改进
对于原生 iOS 开发者来说, 现在可以通过更加符合直觉的方式得到对象和伴随对象. 比如, 如果在 Kotlin 中你有以下对象:
要在 Swift 中访问它们, 你可以使用 shared
和 companion
属性:
详情请参见 与 Swift/Objective-C 代码交互.
对 MinGW 编译目标废弃无导入库的 DLL 链接
LLD 是 LLVM 项目的一个链接器, 我们计划在 Kotlin/Native 中对 MinGW 编译目标使用它, 因为它的好处比默认的 ld.bfd 更多 – 主要是它的性能更好.
但是, LLD 的最新稳定版本对 MinGW (Windows) 编译目标不支持直接链接到 DLL. 这样的链接需要使用 导入库. 尽管对于 Kotlin/Native 1.5.30 来说不需要它们, 但我们添加了一个警告, 告知你这样的使用不兼容于 LLD, 将来 LLD 会成为 MinGW 编译目标的默认链接器.
关于转换到 LLD 浏览器, 请在 这个 YouTrack issue 中提供你的反馈意见.
Kotlin Multiplatform
1.5.30 对于 Kotlin Multiplatform 带来了以下重要更新:
在共用的原生代码中可以使用自定义 cinterop 库
Kotlin Multiplatform 提供了一个 选项, 可以在共用的源代码集中使用平台相关的 interop 库. 在 1.5.30 之前, 这个功能只能用于随 Kotlin/Native 一同发布的 平台库. 从 1.5.30 开始, 你可以使用你自定义的 cinterop
库. 要启用这个功能, 请在你的 gradle.properties
中添加 kotlin.mpp.enableCInteropCommonization=true
属性:
支持 XCFramework
所有的 Kotlin Multiplatform 项目现在可以使用 XCFramework 作为输出格式. Apple 引入了 XCFramework 来替代通用(Universal) (fat) Framework. 通过使用 XCFramework, 你:
可以将所有的编译目标平台和处理器架构的逻辑集中在一个单独的 bundle 中.
在将应用程序发布到 App Store 之前, 不必删除所有不需要的处理器架构.
如果你想要对 Apple M1 上的设备和模拟器使用你的 Kotlin Framework, XCFramework 会很有用.
要使用 XCFramework, 请更新你的 build.gradle(.kts)
脚本:
当你声明 XCFramework 时, 会注册这些新的 Gradle task:
assembleXCFramework
assembleDebugXCFramework
(debug 用 artifact, 包含 dSYMs)assembleReleaseXCFramework
关于 XCFramework, 详情请参见 这个 WWDC 视频.
对 Android artifact 的新的默认发布设置
使用 maven-publish
Gradle plugin, 你可以在构建脚本中指定 Android 变体 名称, 对 Android 编译目标发布你的跨平台库. Kotlin Gradle plugin 会自动生成发布.
在 1.5.30 之前, 生成的发布 metadata 包含每一个发布的 Android 变体的构建类型属性, 因此只能兼容于库使用者所使用的相同的构建类型. Kotlin 1.5.30 引入了一个新的默认发布设置:
如果项目发布的所有 Android 变体拥有相同的构建类型属性, 那么发布的变体不会拥有构建类型属性, 而且能够兼容于任何构建类型.
如果发布的变体拥有不同的构建类型属性, 那么只有带有
release
值的, 发布时会不带有构建类型属性. 因此 release 变体兼容于使者端的任何构建类型, 而 release 之外的其他变体只兼容于匹配的使者端构建类型.
如果要关闭这个功能, 并对所有变体保持构建类型属性, 你可以设置这个 Gradle 属性: kotlin.android.buildTypeAttribute.keep=true
.
Kotlin/JS
在 1.5.30 中, Kotlin/JS 有 2 个主要改进:
JS IR 编译器后端升级为 Beta 版
1.4.0 版引入了 Kotlin/JS 的 基于 IR 的编译器后端, 当时是 Alpha 版, 现在升级为 Beta 版.
以前, 我们发布了 移植到 JS IR 后端的向导, 来帮助你将你的项目移植到新的后端. 现在我们提供 Kotlin/JS Inspection Pack IDE plugin, 它可以直接在 IntelliJ IDEA 中显示需要哪些修改.
使用 Kotlin/JS IR 后端为应用程序带来更好的调试体验
Kotlin 1.5.30 带来了对 Kotlin/JS IR 后端的 JavaScript 源代码映射生成功能. 这个功能可以改善启用 IR 后端时的 Kotlin/JS 调试体验, 支持所有的调试功能, 包括断点, 单步执行, 以及易读的调用栈信息, 带有正确的源代码引用.
Gradle
为了 改进 Kotlin Gradle plugin 使用者体验, 我们实现了以下功能:
支持 Java 工具链
Gradle 6.7 引入了 支持 Java 工具链 功能. 使用这个功能, 你可以:
使用与 Gradle 不同的 JDK 和 JRE 运行编译, 测试, 和可执行文件.
使用未发布的语言版本编译和测试代码.
通过工具链支持, Gradle 可以自动检测本地的 JDK, 并安装 Gradle 构建所需要但缺失的 JDK. 现在 Gradle 自身可以在任何 JDK 上运行, 而且还能够重用 构建缓存功能.
Kotlin Gradle plugin 对 Kotlin/JVM 编译任务支持 Java 工具链. Java 工具链会:
为 JVM 编译目标设置可用的
jdkHome
选项.如果使用者没有明确设置
jvmTarget
选项, 会将kotlinOptions.jvmTarget
设置为工具链的 JDK 版本. 如果工具链没有配置,jvmTarget
域会使用默认值. 详情请参见 JVM 编译目标兼容性.影响
kapt
worker 运行在哪个 JDK 上.
可以使用以下代码来设置一个工具链. 请将 <MAJOR_JDK_VERSION>
替换为你想要使用的 JDK 版本:
注意, 通过 kotlin
扩展设置工具链, 也会对 Java 编译任务更新工具链.
你可以通过 java
扩展设置工具链, Kotlin 编译任务会使用它:
关于为 KotlinCompile
任务设置 JDK 版本, 请参见 使用 Task DSL 设置 JDK 版本.
对于 Gradle 版本 6.1 到 6.6, 请 使用 UsesKotlinJavaToolchain
接口来设置 JDK Home.
使用 UsesKotlinJavaToolchain 接口指定 JDK Home
所有支持通过 kotlinOptions
设置 JDK 的 Kotlin 任务, 现在都实现 UsesKotlinJavaToolchain
接口. 要设置 JDK Home, 请设置你的 JDK 路径, 并替换 <JDK_VERSION>
部分:
对 Gradle 6.1 到 6.6 版本, 请使用 UsesKotlinJavaToolchain
接口. 从 Gradle 6.7 开始, 请改为使用 Java 工具链.
使用这个功能时, 请注意, kapt 任务 worker 只使用 进程隔离模式, kapt.workers.isolation
属性将被忽略.
用更简单的方式明确指定 Kotlin Daemon 的 JVM 参数
在 Kotlin 1.5.30 中, 对于 Kotlin Daemon 的 JVM 参数有了新的逻辑. 以下列表中的每个选项都会覆盖它之前的选项:
如果没有指定任何参数, Kotlin Daemon 会从 Gradle Daemon 继承参数(和以前一样). 比如, 在
gradle.properties
文件中:org.gradle.jvmargs=-Xmx1500m -Xms=500m如果 Gradle Daemon 的 JVM 参数包含
kotlin.daemon.jvm.options
系统属性, 和以前一样使用它:org.gradle.jvmargs=-Dkotlin.daemon.jvm.options=-Xmx1500m -Xms=500m在
gradle.properties
文件中你可以添加kotlin.daemon.jvmargs
属性:kotlin.daemon.jvmargs=-Xmx1500m -Xms=500m你可以在
kotlin
扩展中指定参数:kotlin { kotlinDaemonJvmArgs = listOf("-Xmx486m", "-Xms256m", "-XX:+UseParallelGC") }kotlin { kotlinDaemonJvmArgs = ["-Xmx486m", "-Xms256m", "-XX:+UseParallelGC"] }你可以对一个特定的任务指定参数 :
tasks .matching { it.name == "compileKotlin" && it is CompileUsingKotlinDaemon } .configureEach { (this as CompileUsingKotlinDaemon).kotlinDaemonJvmArguments.set(listOf("-Xmx486m", "-Xms256m", "-XX:+UseParallelGC")) }tasks .matching { it.name == "compileKotlin" && it instanceof CompileUsingKotlinDaemon } .configureEach { kotlinDaemonJvmArguments.set(["-Xmx1g", "-Xms512m"]) }
关于 Kotlin Daemon, 详情请参见 Kotlin Daemon 以及它在 Gradle 中的使用.
标准库
Kotlin 1.5.30 包括对标准库的 Duration
和 Regex
API 的改进:
改变了 Duration.toString() 的输出
在 Kotlin 1.5.30 以前, Duration.toString()
函数会返回它的参数的字符串表达, 使用最紧凑的并且可以阅读的数值单位. 从现在开始, 它会返回一个字符串值, 表达为多个数值部分的组合, 每个数值部分使用自己的单位. 每个部分是一个数值, 加上一个单位缩写名称: d
, h
, m
, s
. 比如:
函数调用示例 | 以前的输出 | 现在的输出 |
---|---|---|
Duration.days(45).toString() |
|
|
Duration.days(1.5).toString() |
|
|
Duration.minutes(1230).toString() |
|
|
Duration.minutes(2415).toString() |
|
|
Duration.minutes(920).toString() |
|
|
Duration.seconds(1.546).toString() |
|
|
Duration.milliseconds(25.12).toString() |
|
|
负值的时间长度表达也发生了同样的变化. 一个负值的时间长度使用负号 (-
) 前缀, 如果它由多个部分组成, 会用括号括起: -12m
和 -(1h 30m)
.
注意, 少于 1 秒的时间长度表达为单个数值, 使用秒以下单位的. 比如, ms
(毫秒), us
(微秒), 或 ns
(纳秒): 140.884ms
, 500us
, 24ns
. 不再使用科学记数法来表达.
如果你想要使用单个单位来表达时间长度, 请使用超载函数 Duration.toString(unit, decimals)
.
从字符串解析 Duration
在 Kotlin 1.5.30 中, 有以下新的 Duration API 函数:
parse()
, 支持解析以下函数的输出:parseIsoString()
, 只解析toIsoString()
输出的格式.parseOrNull()
和parseIsoStringOrNull()
, 与上面的函数类似, 但对于无效的 Duration 格式, 会返回null
而不是抛出IllegalArgumentException
异常.
下面是 parse()
和 parseOrNull()
的一些使用示例:
下面是 parseIsoString()
和 parseIsoStringOrNull()
的一些使用示例:
在一个指定的位置匹配正规表达式
新的 Regex.matchAt()
和 Regex.matchesAt()
函数提供一种方法, 可以检查一个正规表达式在一个 String
或 CharSequence
的指定位置是否存在完全匹配.
matchesAt()
返回一个 boolean 结果:
如果找到匹配, 则 matchAt()
返回匹配结果, 否则返回 null
:
使用正规表达式将字符串切分为一个序列
新的 Regex.splitToSequence()
函数是 split()
函数的 lazy 版本. 它根据正规表达式的匹配结果切分字符串, 但结果返回为一个 序列(Sequence), 因此对这个结果的所有操作都会以 lazy 模式执行.
对 CharSequence
也添加了一个类似的函数 :
Serialization 的 1.3.0-RC 版
发布了 kotlinx.serialization
1.3.0-RC 版, 包括新的 JSON 序列化功能:
Java IO Stream 序列化
对默认值的属性级控制
一个选项, 可以在序列化中排除 null 值
在多态序列化中使用自定义类区分
详情请参见 变更列表.