终于,Kotlin 1.4的第一个预览版发布了,在新版本1.4-M1中,Kotlin又添加了一些新的功能,同时,也有一些重大的改进。本篇文章就带大家一起看看新版Kotlin中有哪些我们期望添加和改进的功能。
如果使用在线编程,浏览器打开play.kotlinlang.org/,然后可以选择Kotlin版本为1.4-M1
如果使用的是Android Studio 或者IntelliJ IDE,你可以直接升级插件到最新版本1.4-M1,步骤如下:
Early Access Preview X
,选择对应版本
在Kotlin1.4中,使用了一个新的功能更加强大的类型推荐算法,或许你在Kotlin1.3中已经尝试过这个算法了,在Kotlin1.3中,通过指定编译器选项可以实现。但是现在默认就使用它了。关于新的算法一些详细的信息,可以查看:youtrack.jetbrains.com/issues/KT?q… 下面只介绍一些重要的改进。
终于等到你,Kotlin1.4中可以支持Kotlin interface SAM转换了,这个真的太重要的了。
什么是SAM转换
?可能有的同学还不太了解,这里先科普一下:
SAM 转换,即 Single Abstract Method Conversions,就是对于只有单个非默认抽象方法接口的转换 —— 对于符合这个条件的接口(称之为 SAM Type ),在 Kotlin 中可以直接用 Lambda 来表示 —— 当然前提是 Lambda 的所表示函数类型能够跟接口的中方法相匹配。
在Kotlin1.4之前,Kotlin是不支持Kotlin的SAM转换的,可以支持Java SAM转换,官方给出的的解释是:是 Kotlin 本身已经有了函数类型和高阶函数,不需要在去SAM转化。 这个解释开发者并不买账,如果你用过Java Lambda和Fuction Interface。当你切换到Kotlin时,就会很懵逼。看来Kotlin是意识到了这个,或者是看到开发者的反馈,终于支持了。
Kotlin 的SAM转换是什么样子呢?一起看一个对比
1.4
之前:
1.4
之后:
// 注意需用fun 关键字声明 fun interface Action { fun run() } fun runAction(a: Action) = a.run() fun main() { // 传递一个对象,OK runAction(object : Action{ override fun run() { println("run action") } }) // 1.4-M1支持SAM,OK runAction { println("Hello, Kotlin 1.4!") } } 复制代码
可以看到,在1.4之前,只能传递一个对象,是不支持Kotlin SAM的,而在1.4之后,可以支持Kotlin SAM,但是用法有一丢丢变化。interface需要使用fun
关键字声明。使用fun关键字标记接口后,只要将此类接口作为参数,就可以将lambda作为参数传递。
新的推理算法在许多情况下会推断类型
,在这些情况下,旧的推理需要显示指定它们的类型。例如,在下面的示例中,会将lambda
参数的类型正确推断为String?
:
val rulesMap: Map<String, (String?) -> Boolean> = mapOf( "weak" to { it != null }, "medium" to { !it.isNullOrBlank() }, "strong" to { it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) } ) fun main() { println(rulesMap.getValue("weak")("abc!")) println(rulesMap.getValue("strong")("abc")) println(rulesMap.getValue("strong")("abc!")) } 复制代码
在1.3版本中,上面的代码IDE是会报错的
,需要引入一个显式的lambda参数,或将to
替换为具有显式泛型参数的Pair构造函数以使其起作用。改为像下面这样:
//需要显示的lambda 参数 val rulesMap: Map<String, (String?) -> Boolean> = mapOf( "weak" to { it -> it != null }, "medium" to { it -> !it.isNullOrBlank() }, "strong" to { it -> it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) } ) fun main() { println(rulesMap.getValue("weak")("abc!")) println(rulesMap.getValue("strong")("abc")) println(rulesMap.getValue("strong")("abc!")) } 复制代码
打印结果如下:
true true false 复制代码Process finished with exit code 0 复制代码
在Kotlin 1.3中,除非指定类型,否则lambda内的最后一个表达式不能智能强制转换。因此,在以下示例中,Kotlin 1.3推断String?
作为结果变量的类型:
val result = run { var str = currentValue() if (str == null) { str = "test" } str // Kotlin编译器知道str在这里不为null } // result的类型在kotlin1.3中推断为String?,在Kotlin1.4中为String 复制代码
但在Kotlin 1.4中,由于使用了新的推理算法,lambda
内部的最后一个表达式得到了智能转换
,并且此新的更精确的类型用于推断所得的lambda类型。因此,结果变量的类型变为String。而在Kotlin 1.3中,通常需要添加显式强制转换(!!
或键入诸如String之类的强制转换
)以使这种情况起作用,现在这些强制转换已不再需要了。
请看下面的示例代码:
sealed class Animal class Cat : Animal() { fun meow() { println("meow") } } class Dog : Animal() { fun woof() { println("woof") } } fun perform(animal: Animal) { val kFunction: KFunction<*> = when (animal) { is Cat -> animal::meow is Dog -> animal::woof } kFunction.call() } fun main() { perform(Cat()) } 复制代码
在kotlin 1.3中,你无法访问智能转换类型引用的成员,但是现在可以了。
在将 Animal
变量智能地强制转换为特定类型的Cat
和Dog
之后,可以使用不同的成员引用animal :: meow
和animal :: woof
。在检查类型之后,就可以访问与子类型相对应的成员引用了。
比如下面这个列子:
fun foo(i: Int = 0): String = "$i!" fun apply1(func: () -> String): String = func() fun apply2(func: (Int) -> String): String = func(42) fun main() { println(apply1(::foo)) println(apply2(::foo)) } 复制代码
在Kotlin 1.3中,foo
函数解释为一个带Int参数的函数,因此,apply1
会报类型错误。
而现在,使用具有默认参数值的函数的可调用引用得到优化,foo
函数的可调用引用可以解释为采用一个Int参数
或不采用任何参数
。因此就不会报上面的类型错误了。
先来看一段代码:
fun main() { var prop: String? by Delegates.observable(null) { p, old, new -> println("$old → $new") } prop = "abc" prop = "xyz" } 复制代码
以上代码在Kotlin 1.3 上编译不过,因为在分析by
后面的委托表达式时,不会考虑委托属性的类型,因此会报类型错误。但是现在的kotlin 1.4-M1中,编译器会正确推断old
和new
参数类型为String?
。
在1.3.0版中,我们不推荐使用kotlin.coroutines.experimental
API,而推荐使用kotlin.coroutines
。在1.4-M1中,我们将从标准库中删除kotlin.coroutines.experimental
完成弃用。对于那些仍然在JVM上使用它的,我们提供了一个兼容库: kotlin-coroutines-experimental-compat.jar
来替换它。我们将其与Kotlin 1.4-M1一起发布到了Bintray上。
mod
操作符另一个不建议使用的函数是数字类型的mod
运算符,该运算符可计算除法运算后的余数。在Kotlin 1.1中,它被rem()
函数取代。现在,将其从标准库中完全删除。
标准库中包含了一些将浮点类型的转换为整数类型的方法,如:toInt()
, toShort()
, toByte()
。但是由于数值范围狭小且变量大小较小,将浮点数转换为Short和Byte可能会导致意外结果。为了解决这个问题,在1.4-M1中,我们废弃了Double
和Float
中的toShort()
和toByte()
方法。如果你仍然想吧浮点类型转化为Short或者Byte,该怎么办呢?那也好办,进行两步转换,先将浮点类型转为Int,然后再将Int转为目标类型就可以了。
我们修改了通用反射API。现在,它包含所有三个目标平台(JVM,JS,Native)上可用的成员,因此现在可以确保相同的代码可在其中任何一个平台上上工作了。
从1.4-M1开始,我们在kotlin-reflect.jar
中嵌入了Kotlin Reflection的Proguard / R8
配置, 有了这个更改,大多数使用了R8或者Proguard的Android项目在不用其他任何配置的情况下使用kotlin-reflect。你不再需要复制粘贴Kotlin反射的Proguard规则。但是请注意,你仍然需要明确列出所有要考虑反射的API。
从1.3.70版开始,Kotlin能够在JVM字节码(目标版本1.8+)中生成类型注解,以便它们在运行时可用。社区要求此功能已有一段时间,因为它使使用某些现有Java库变得更加容易,并为新库的作者提供了更多的扩展能力。
在以下示例中,可以在字节码中发出String类型的@Foo
批注,然后由库代码使用:
@Target(AnnotationTarget.TYPE) annotation class Foo class A { fun foo(): @Foo String = "OK" } 复制代码
关于具体如何使用,可以看一下这篇博客:blog.jetbrains.com/kotlin/2020…
除了上面的一些改动之外,对于Kotlin/Js和Kotlin/iOS 平台也有一些优化和改进,大致列出来看一下:
在kotlin.js
和multiplatform
Gradle插件中,引入了新的重要设置。在build.gradle.kts
文件的目标块内,如果您想在构建过程中生成.js
工件,则可以配置并使用produceExecutable()
。
kotlin { target { useCommonJs() produceExecutable() browser {} } } 复制代码
如果您正在编写Kotlin / JS库,则可以省略ProduceExecutable()
配置。
当使用新的IR编译器后端(有关此内容的更多详细信息,在下文中)时,省略此设置意味着将不会生成可执行的JS文件(因此,构建过程将运行得更快)。将在build / libs文件夹中生成一个klib文件,该文件可从其他Kotlin / JS项目使用,也可作为同一项目中的依赖项。如果您未明确指定produceExecutable()
,则默认情况下会发生这种情况。
使用produceExecutable()将生成可从JavaScript生态系统执行的代码,无论其具有自己的入口点还是作为JavaScript库,这将生成实际的JavaScript文件,该文件可以在节点解释器中运行,可以嵌入HTML页面中并在浏览器中执行,或用作JavaScript项目的依赖项。
Kotlin 1.4-M1是第一个包含针对Kotlin / JS目标的新IR编译器后端的版本。此后端是极大改进的基础,也是Kotlin / JS与JavaScript和TypeScript交互方式发生某些变化的决定性因素。以下突出显示的几个功能均针对新的IR编译器后端。虽然默认情况下尚未启用它,我们鼓励你在项目中尝试以下它。
如何使用新的后端?
在gradle.properties
配置文件中添加以下配置:
kotlin.js.compiler=ir // or both 复制代码
如果需要为IR编译器后端和默认后端生成库,则可以选择将此标志设置为both
。
关于both
的作用请看下面的章节介绍。
新的IR编译器后端与原来默认的后端相比主要的变换是没有二进制兼容,Kotlin / JS的两个后端之间缺乏这种兼容性,这意味着使用新的IR编译器后端创建的库无法从默认后端使用,反之亦然。
与默认后端相比,新的IR编译器后端进行了很多优化。生成的代码与静态分析器配合使用效果更好了,甚至可以通过Google的Closure Compiler从新的IR编译器后端运行生成的代码,并使用其高级优化模式。
现在,标记为public的声明不再自动导出,要使顶级声明能在JavaScript或TypeScript中使用,请使用@JsExport
注解。
package blogpost @JsExport class KotlinGreeter(private val who: String) { fun greet() = "Hello, $who!" } @JsExport fun farewell(who: String) = "Bye, $who!" fun secretGreeting(who: String) = "Sup, $who!" // only from Kotlin! 复制代码
新的编译器支持从Kotlin代码生成TypeScript定义,对于配置produceExecutable()
配置项,并且使用了上面的@JsExport
的顶级声明,将生成带有TypeScript定义的.d.ts
文件。如上面的代码,生成的文件如下所示:
// [...] namespace blogpost { class KotlinGreeter { constructor(who: string) greet(): string } function farewell(who: string): string } // [...] 复制代码
Kotlin的早期版本为Objective-C互操作中的泛型提供了实验性支持。要从Kotlin代码生成具有泛型的框架头,必须使用-Xobjc-generics
选项。在1.4-M1中,默认就支持范型了。但在某些情况下,这可能会破坏现有的调用Kotlin框架的Objective-C或Swift代码。如果不想使用范型,请添加-Xno-objc-generics
选项
binaries.framework { freeCompilerArgs += "-Xno-objc-generics" } 复制代码
在1.4中,我们略微更改了从Kotlin生成Swift API异常处理的方式。Kotlin和Swift的错误处理存在根本的不同,所有Kotlin异常均未经检查,而Swift仅检查错误。因此,为了使Swift代码感知异常,需使用@Throws
注解标记Kotlin函数,该注解指定潜在异常类的列表。
当编译为Swift或Objective-C框架时,具有或正在继承@Throws
注解的函数在Objective-C中表示为NSError *
处理方法,而在Swift中表示为throws
方法。
我们一直在努力提高Kotlin / Native编译和执行的整体性能。1.4-M1中,我们为提供了新的对象分配器,在某些基准测试中,它的运行速度提高了两倍。当前,新的分配器是实验性的,默认情况下不使用。您可以使用-Xallocator = mimalloc
切换至该选项。
以上就是Kotlin1.4-M1的一些变化,其中最令我惊喜的一个功能是:终于支持Kotlin interface SAM 转换了。其他的一些功能大家都可以去试一下,更多更详细的信息请去官网了解,期待早点出release版吧!