我们很高兴迎来了重要版本的第一个预览版: Kotlin 1.4-M1.
几个月前,我们发布了展望Kotlin 1.4的博客。在临近正式发布之时,我们将提供预览版,让你能亲自尝试部分新功能。

在这篇博文中,我们将重点介绍1.4-M1以下新功能和关键改进:
- 默认情况下,已开启了一个新的,更强大的类型推断算法。
- 契约现已支持final成员函数了。
- Kotlin/JVM编译器现已会在Java 8+目标的字节码中生成类型注解。
- 针对Kotlin/JS的新后端,并对产生的组件进行了重大改进。
- 标准库变动:deprecated周期结束,同时弃用额外的部分。
你可以在更新日志查看完整的变动列表。一如既往,我们非常感谢其他贡献者。
我们强烈建议你尝试预览版,我们将非常感激你在问题跟踪器中提供的所有反馈。
更强大的类型推断算法
Kotlin 1.4使用了新的更强大的类型推断算法。在Kotlin 1.3中,你可以通过指定编译器选项来尝试这种算法,1.4中已默认开启。你可以在YouTrack中查阅新算法所解决问题的完整列表。在这篇文章中,我们将重点介绍最明显的改进。
SAM类型的Kotlin函数和接口
SAM意味着当接口”只有单一抽象方法”时,可以将其简化为lambda。在以前,SAM转换只能在Kotlin的Java方法和Java接口上执行,现在你也可以在Kotlin函数和接口上使用了。
尽管Kotlin接口现在支持SAM转换。但注意它的工作方式不同于Java:需要你显式标记函数和接口。在使用fun
关键字标记接口后,只要参数是该接口,就可以向其传递一个lambda了:
可以阅读之前的博文了解有关SAM的更多细节。
Kotlin一开始就支持Java接口的SAM转换,但是有一种情况不被支持,便是部分Java库有时会很烦人。如果调用以两个SAM接口作为参数的Java方法,则两个参数都必须同为lambda或对象。不能向其中一个参数传递lambda,而向另一个参数传递对象。新算法解决了这个问题,你可以随意传递lambda而不是SAM接口,我们认为这能符合你的预期。
适配更多场景的类型推断算法
在更多的使用场景下,旧的推断算法需要你显式声明,而新的推断算法则能正确推断类型了。例如在下面的示例中,lambda的参数it
的类型被正确推断为String ?
了:
在Kotlin 1.3,你需要引入一个显式的lambda参数,或将to
替换为构造函数具有显式泛型参数的Pair
,才能正常工作。
对lambda结尾表达式的智能转换
在Kotlin 1.3中,除非你指定类型,否则lambda内的最后一个表达式不会执行智能转换。因此以下示例中,Kotlin 1.3会将result
变量的类型推断为String?
:
而在Kotlin 1.4,得益于新的推断算法,lambda中最后一个表达式也会被智能转换了,因此,lamda表达式的结果类型将被推断为新的更精确的类型。则result
变量的类型最终为String
。
在Kotlin 1.3,经常需要你指定显式转换(如!!
或as String
这样的强制转换)以实现这样的效果,而现在已经不需要添加这样的强制转换了。
可执行引用的智能转换
在Kotlin 1.3,你无法访问经过智能转换的类型的成员函数。但现在你可以这样:
在animal变量经过智能转换到指定的Cat
或Dog
类型后,可以访问不同的成员函数animal::meow
和animal::woof
。
在类型检查之后,便可以访问与子类型对应的成员函数。
更优异的可执行引用推断
现在,传递带默认参数函数的可执行引用更加方便了。例如,以下foo函数
的可执行引用可以理解为需要一个Int
参数或不需要任何参数:
更优异的代理属性推断
在解析紧跟关键字by
的委托表达式时,不会去判断委托属性的类型。例如,以下代码在以前无法编译,但是现在编译器能正确地将参数old
和参数new
的类型推断为String?
了:
语言变化
之前的博文已介绍了大部分的语言变化了:
- SAM conversions for Kotlin classes
- Mixing named and positional arguments
- Optimized delegated properties
- Trailing Commas
- Break & continue inside when
- Changes for tail-recursive functions
在这一篇文章里,我们将着重介绍有关契约的小改进。
支持契约
虽然自定义契约语法仍处于试验阶段,但是我们已经支持了部分会用到契约的新用例。
现在你可以使用reified的泛型参数来定义契约。例如,你可以为assertIsInstance
函数实现如下契约:
由于T
类型参数是reified,因此可以在函数体中检查其类型。现在在契约中也可以如此实现了。后续将向kotlin.test
库添加一个类似的信息断言函数。
另外,你现在可以为final
成员自定义契约了。此前是禁止向成员函数定义契约的,因为在层级的部分成员上定义契约意味着相关的契约也具有层级结构,而这仍然是需要设计和讨论的问题。但如果成员函数是final
函数且未覆写任何其他函数,则可以安全地为其定义契约。
标准库的变动
清理废弃的实验协程
在1.3.0版中,kotlin.coroutines.experimental
API已被标记deprecated,它的替代为kotlin.coroutines
。在1.4-M1中,我们从标准库中删除了kotlin.coroutines.experimental
以结束其deprecated的流程。对于仍然在JVM上使用它的用户,我们提供了一个兼容包kotlin-coroutines-experimental-compat.jar
及所有实验性的协程API。我们将其发布到了Maven,并将其包含在标准库旁边的Kotlin发行版中。同时与1.4-M1组件一起发布到了Bintray仓库。
删除废弃的mod运算
另一个废弃的函数是mod
操作符,该运算符可获取除法运算的余数。在Kotlin 1.1中,它被rem()
函数取代。而现在我们将其从标准库中完全删除。
弃用浮点数向Byte和Short类型的转换
标准库包含将浮点数转换为整数类型的函数:toInt()
,toShort()
,toByte()
。由于数值范围狭窄且变量存储体积较小,将浮点数转换为Short
和Byte
可能会导致意外结果。为避免此类问题,从1.4-M1开始,我们不建议在Double
和Float
上调用toShort()
和toByte()
函数。如果仍然需要将浮点数转换为Byte
或Short
,请执行两次转换:先转换为Int
,然后转换为目标类型。
通用反射API
我们修改了通用反射API。现在,它包含了所有的三个目标平台(JVM,JS,Native)上可用的成员,因此你可以确保同一份代码可在任一平台上工作。
use()和时间测量函数的新契约
我们正在扩大标准库中契约 的使用范围。在1.4-M1中,我们为单次执行代码块use()
函数以及时间测量函数measureTimeMillis()
和measureNanoTime()
添加了契约声明。
用于Kotlin反射的Proguard配置
从1.4-M1开始,我们在kotlin-reflect.jar
中内嵌了Kotlin Reflection的Proguard/R8配置。有了这个,就能使大多数R8或Proguard的Android项目可以在没有其他配置的情况下使用kotlin-reflect进行工作了。不再需要你去复制粘贴Kotlin反射包内部的Proguard配置。但是请注意,你仍然需要明确罗列所有用到反射的API。
Kotlin/JVM
从1.3.70版开始,Kotlin能够在JVM字节码(目标版本1.8+)中生成类型注解,以便在运行时能获取到它们。社区要求这个特性有一段时间了,因为能让使用部分现有Java库变得更加容易,并为新库的作者提供了更多功能。
在以下示例中,可以向字节码中输出String
类型的@Foo
注解,然后由库代码使用:
有关如何在字节码中输出类型注解的更多细节,请查阅Kotlin 1.3.70版本发布博文的相应部分。
Kotlin/JS
对于 Kotlin/JS,该里程碑版本包括对Gradle DSL的部分更改,它是第一个包含支持优化和新特性的新 IR 编译器后端的版本。
Gradle DSL的变化
在kotlin.js
和多平台
Gradle插件中,引入了新的重要配置。在build.gradle.kts
文件的目标块内,可以通过produceExecutable()
设置以在构建过程中生成.js
组件。
如果你正在编写Kotlin/JS库,则可以省略produceExecutable()
。当使用新的IR编译器后端(后文有该内容的更多相关详细信息)时,省略该设置意味着不会生成可执行的JS文件(因此,构建过程将更快)。同时会在build/libs
文件夹中生成一个klib
文件,该文件可被其他Kotlin/JS项目使用,也能被同一项目所依赖。这是你不明确指定produceExecutable()
时的默认行为。
使用produceExecutable()
会生成JavaScript生态可执行的代码,无论是以其本身作为入口还是作为JavaScript库。这会生成实际的JavaScript文件,这些文件可以在节点解释器中运行,可以嵌入HTML页面并在浏览器中执行,或用作JavaScript项目的依赖。
请注意,指定新的IR编译器后端时(下文将介绍更多信息),produceExecutable()
将始终为每个目标生成一个独立的.js
文件。当前不支持在多个生成的组件之间进行重复数据删除或拆分代码。你可以期待produceExecutable()
后续里程碑版本中该行为会有所变化。而该配置项的命名将来也会变更。
新的编译器后端
Kotlin 1.4-M1是第一个包含新IR编译器后端(面向Kotlin/JS)的版本。该后端是进行大优化的基础,也是决定Kotlin/JS与JavaScript及TypeScript的交互方式发生变化的因素。下文强调的特性均针对新的IR编译器后端。虽然默认未启用,但我们建议你在项目中去尝试,并开始为新的后端准备你的代码库,当然希望能给我们提供反馈并记录遇到的任何问题。
Using the new backend
要开始使用新的后端,请在gradle.properties
文件中如下配置:
如果需要为IR编译器后端和默认后端生成代码库,则可以将其配置项设为both
。该配置的确切作用在标题为“Both-mode”的博文中有所介绍。由于新旧编译器后端非二进制兼容,因此该标志是必需的。
二进制不兼容
新的IR编译器后端的主要变化是与默认后端没有二进制兼容性。 Kotlin/JS的两个后端之间缺乏这种兼容性,这意味着通过新的IR编译器后端创建的库无法在默认后端中使用,反之亦然。
如果要为你的项目使用IR编译器后端,则需要将所有Kotlin依赖更新为支持新后端的版本。JetBrains面向Kotlin/JS发布的针对Kotlin 1.4-M1的库已经包含新IR编译器后端运行所需要的所有组件。当依赖于这些库时,Gradle会自动选择正确的组件(无需指定IR的类型)。请注意,某些库(例如kotlin-wrappers
)在新的IR编译器后端中存在一些问题,因为它们依赖于默认后端的专属特性。我们已经知道这一问题,并将在未来着力去改善。
如果你是库的作者,希望了解当前编译器后端以及新的IR编译器后端的兼容性,请另外阅读该博文的“Both-mode”部分。
下一节将详细介绍新编译器带来的好处和区别。
DCE优化
与默认后端相比,新的IR编译器后端能够进行更积极的优化。生成的代码与静态分析器配合使用有更好的效果,甚至可以通过Google的Closure Compiler从新的IR编译器后端运行生成的代码,并使用高级优化模式(请注意Kotlin/JS Gradle插件未为其提供具体支持)。
最明显变化是生成组件的代码体积。一种消除无用代码的改进方案使组件大幅度精简。例如“Hello, World!”的Kotlin/JS程序小于1.7 KiB。而对于更复杂的(演示)项目,即便是使用了kotlinx.coroutines
的示例项目,其数字也已经发生了巨大变化,希望你能亲自去体验:
DEFAULT BACKEND | IR BACKEND | |
---|---|---|
After compilation | 3.9 MiB | 1.1 MiB |
After JS DCE | 713 KiB | 430 KiB |
After bundle | 329 KiB | 184 KiB |
After ZIP | 74 KiB | 40 KiB |
如果你对此有所怀疑,请尝试一下。默认情况下,在Kotlin 1.4-M1中,两个后端都启用了DCE和bundling!
导出声明到JavaScript
使用IR编译器后端时,将不再自动导出标记为public的声明(名称混淆过的版本也不会导出)。这是因为IR编译器的closed-world模型的设计是,要导出的声明需要进行特别的注解,这是有助于上述优化的其中一个因素。
要让顶层声明可从外部的JavaScript或TypeScript中访问,请使用@JsExport
注解。在以下示例中,我们让KotlinGreeter
(及其方法)和farewell()
在JavaScript可访问,但secretGreeting()
只在Kotlin可访问:
预览: TypeScript定义
我们很高兴展示新Kotlin/JS IR编译器的另一个特性,就是从Kotlin代码生成TypeScript定义。当在开发混合应用程序时,JavaScript工具和IDE可以通过这些定义来实现自动补全,同时支持静态分析器,并使JS和TS项目中混合Kotlin代码更加容易。
对于使用了ProduceExecutable()
配置的项目中标有@JsExport
的顶级声明(见上文),将生成带有TypeScript定义的.d.ts
文件。对于上面的代码段,它们如下所示:
在Kotlin 1.4-M1中,可以在build/js/packages/package_name/kotlin
中找到这些声明,以及相应未打包的JavaScript代码。请注意,由于只是预览,因此默认情况下它们暂不会被添加到distributions
文件夹中。当然未来可能会有变化。
Both-mode
了使库的维护者能更轻松地迁移到新的IR编译器后端,gradle.properties
引入了kotlin.js.compiler
配置:
在both
模式下,会同时使用IR编译器后端和默认编译器后端(因此命名)从代码源集来构建库。这意味着Kotlin IR的klib
文件和默认编译器的js
文件将同时生成。若发布到同一个Maven坐标时,Gradle将根据用例自动选择合适的组件-js
用于旧编译器,klib
用于新编译器。这表示针对已经升级到Kotlin 1.4-M1并且使用两个编译器后端其中之一的项目,你可以使用新的IR编译器后端编译和发布库了。它可以确保在使用你的库及默认后端的用户体验不被破坏,因为他们已经将项目升级到了 1.4-M1。
注意使用both
模式构建依赖及当前项目时,仍然存在导致IDE无法正确解析库引用的问题。我们已意识到这个问题,并将尽快解决。
Kotlin/Native
默认支持Objective-C泛型
Kotlin的早期版本互操作中为Objective-C的泛型提供了实验性支持。要让Kotlin代码生成具有泛型的框架头,必须使用-Xobjc-generics
的编译器选项。在1.4-M1中已默认启用。在某些情况下,这可能会破坏现有调用了Kotlin框架的Objective-C或Swift代码。要在不使用泛型的情况下编写框架头,请添加-Xno-objc-generics
编译器选项。
请注意,文档中列出的所有细节和限制仍然有效。
Objective-C/Swift互操作中异常处理的变更
在1.4中,我们略微更改了从Kotlin生成的关于异常转换方面的Swift API。 Kotlin和Swift在异常处理上存在根本差异。Kotlin没有异常检查,而Swift只检查error。因此,为了使Swift代码能捕获预期的异常,应该使用@Throws
注解标记Kotlin函数,该注解罗列了潜在的异常类型。
当编译成Swift或Objective-C框架时,被@Throws
或其子类标记的函数在Objective-C中表示为NSError \*
,而在Swift中表示为throws
方法。
此前,除RuntimeException
和Error
以外的任何异常都作为NSError
传递。在1.4-M1我们对此行为作了改动。现在,只有仅被@Throws
注解标记的类实例(或其子类)引发异常才抛出NSError
。而Swift/Objective-C的其他Kotlin异常均被视为未处理,并导致程序终止。
性能改进
我们一直在努力提高Kotlin/Native编译和运行的整体性能。在1.4-M1中,我们为你提供了新的对象分配器,该对象分配器在某些基准测试中的运行速度提高了两倍。当前,新的分配器是实验性的,不在默认情况下使用。你可以通过-Xallocator=mimalloc
编译器选项切换至分配器。
兼容性
请注意在某些极端情况下,Kotlin 1.4不向后兼容1.3。语言委员会仔细审查了所有该类情况,并将其列入“兼容性指南”(类似于这一篇)。现在你可以在YouTrack中找到该列表。
重载的解析规则可能会略有变化。如果你有多个相同名称但不同签名的函数,则在Kotlin 1.4中调用可能会与在Kotlin 1.3中选择的函数有所不同。但这仅在某些极端情况下会发生,并且我们希望在实践中这种情况尽量少发生。我们还假设在实践中,重载函数会有类似行为,最终会互相调用,这就是为什么这些更改不会影响程序的行为。但如果你想通过泛型和不同层级的多个重载编写复杂的代码,请留意这一点。所有该类情况都会罗列在上述兼容性指南中。
发行前须知
请注意,向后兼容性不保证包含预发行版本。其特性和API可能在后续版本中更改。当到达最终RC时,编译器将禁止所有预发行版本产生的二进制文件,并且将需要你重新编译1.4‑Mx编译过的所有内容。
如何尝试
同样的,你可以在play.kotl.in使用在线Kotlin。
在IntelliJ IDEA和Android Studio,你可以更新Kotlin插件到1.4-M1版本。操作指南。
如果要在现有项目使用预览版,则需要在Gradle或Maven中为预览版本配置构建。
你可以在Github发行页下载命令行编译器。
你可以使用随版本发行的库的以下版本:
- kotlinx.atomicfu版本:0.14.2-1.4-M1
- kotlinx.coroutines版本:1.3.5-1.4-M1
- kotlinx.serialization版本:0.20.0-1.4-M1
- ktor版本:1.3.2-1.4-M1
在这里查看有关发行版的更多细节和兼容库列表。
分享你的反馈
如果你发现错误并向我们的问题跟踪器YouTrack进行报告,我们表示非常感谢。我们尝试在最终发行版前解决所有重要问题,这意味着无需等到下一个Kotlin发行版你的问题便能得到解决。
如果你有任何疑问并想参与讨论,欢迎加入Kotlin Slack(在这里获得邀请)的#eap频道。在该频道中,你还可以获取有关新预览版的通知。
Let’s Kotlin!
其他贡献者
我们要特别感谢Zac Sweers为Proguard配置嵌入到kotlin-reflect
中所做的贡献。
同样感谢提交的PR合并到该版本中的所有其他贡献者:
- Steven Schäfer
- Toshiaki Kameyama
- pyos
- Mads Ager
- Mark Punzalan
- Juan Chen
- Kristoffer Andersen
- Alfredo Delli Bovi
- Jinseong Jeon
- Jonathan Leitschuh
- Sebastian Schuberth
- Ivan Gavrilovic
- David Schreiber-Ranner
- Miguel Serra
- Alex Chmyr
- Fleshgrinder
- Aleksey Kladov
- Will Boyd
- Dominic Fischer
- Kenji Tomita
- Stéphane Nicolas
- Tillmann Berg
- Kevin Bierhoff
- Tsvetan
Pingback引用通告: Kotlin 1.4-M2正式发布 - KotlinCn
Pingback引用通告: Kotlin 1.4-M3带着标准库改动来了! - KotlinCn
Pingback引用通告: Kotlin 1.4.0-RC! - KotlinCn