Kotlin1.4-M1发布,终于支持Kotlin interface SAM转换了!

终于,Kotlin 1.4的第一个预览版发布了,在新版本1.4-M1中,Kotlin又添加了一些新的功能,同时,也有一些重大的改进。本篇文章就带大家一起看看新版Kotlin中有哪些我们期望添加和改进的功能。


1. 如何使用新版本?

如果使用在线编程,浏览器打开https://play.kotlinlang.org/,然后可以选择Kotlin版本为1.4-M1

如果使用的是Android Studio 或者IntelliJ IDE,你可以直接升级插件到最新版本1.4-M1,步骤如下:

    1. 选择Tools -> Kotlin ->Configure Kotlin Plugin Updates.
    1. 在更新列表中选择Early Access Preview X,选择对应版本
    1. 点击install 安装重启,就完成配置了。

2. 功能更强大的类型推荐算法

在Kotlin1.4中,使用了一个新的功能更加强大的类型推荐算法,或许你在Kotlin1.3中已经尝试过这个算法了,在Kotlin1.3中,通过指定编译器选项可以实现。但是现在默认就使用它了。关于新的算法一些详细的信息,可以查看:https://youtrack.jetbrains.com/issues/KT?q=Tag:%20fixed-in-new-inference%20&_ga=2.58428450.988595807.1586745008-1408654980.1539842787


下面只介绍一些重要的改进。

2.1. Kotlin方法和接口的SAM转换

终于等到你,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作为参数传递。

2.2. 更多场景的自动类型推断

新的推理算法在许多情况下会推断类型,在这些情况下,旧的推理需要显示指定它们的类型。例如,在下面的示例中,会将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
2.3. Lambda内最后一个表达式的智能类型转换

在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之类的强制转换)以使这种情况起作用,现在这些强制转换已不再需要了。

2.4. 可调用类型(Callable)引用的智能转换

请看下面的示例代码:

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变量智能地强制转换为特定类型的CatDog之后,可以使用不同的成员引用animal :: meowanimal :: woof。在检查类型之后,就可以访问与子类型相对应的成员引用了。

2.5. 可调用(Callable)引用优化

比如下面这个列子:

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参数不采用任何参数。因此就不会报上面的类型错误了。

2.6.委托属性优化

先来看一段代码:

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中,编译器会正确推断oldnew参数类型为String?

3.标准库更改

3.1. 废弃试验性的协程API

在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上。

3.2. 删除已废弃的mod操作符

另一个不建议使用的函数是数字类型的mod运算符,该运算符可计算除法运算后的余数。在Kotlin 1.1中,它被rem()函数取代。现在,将其从标准库中完全删除。

3.3. 废弃从浮点类型到Byte和Short的转换

标准库中包含了一些将浮点类型的转换为整数类型的方法,如:toInt(), toShort(), toByte()。但是由于数值范围狭小且变量大小较小,将浮点数转换为Short和Byte可能会导致意外结果。为了解决这个问题,在1.4-M1中,我们废弃了DoubleFloat中的toShort()toByte()方法。如果你仍然想吧浮点类型转化为Short或者Byte,该怎么办呢?那也好办,进行两步转换,先将浮点类型转为Int,然后再将Int转为目标类型就可以了。

3.4. 通用的发射API

我们修改了通用反射API。现在,它包含所有三个目标平台(JVM,JS,Native)上可用的成员,因此现在可以确保相同的代码可在其中任何一个平台上上工作了。

3.5. 用于Kotlin反射的Proguard配置

从1.4-M1开始,我们在kotlin-reflect.jar中嵌入了Kotlin Reflection的Proguard / R8配置, 有了这个更改,大多数使用了R8或者Proguard的Android项目在不用其他任何配置的情况下使用kotlin-reflect。你不再需要复制粘贴Kotlin反射的Proguard规则。但是请注意,你仍然需要明确列出所有要考虑反射的API。

4. Kotlin/JVM

从1.3.70版开始,Kotlin能够在JVM字节码(目标版本1.8+)中生成类型注解,以便它们在运行时可用。社区要求此功能已有一段时间,因为它使使用某些现有Java库变得更加容易,并为新库的作者提供了更多的扩展能力。

在以下示例中,可以在字节码中发出String类型的@Foo批注,然后由库代码使用:

@Target(AnnotationTarget.TYPE)
annotation class Foo

class A {
    fun foo(): @Foo String = "OK"
}

关于具体如何使用,可以看一下这篇博客:https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-3-70-released/#kotlin-jvm

5. 其他一些改动

除了上面的一些改动之外,对于Kotlin/Js和Kotlin/iOS 平台也有一些优化和改进,大致列出来看一下:

5.1 Kotlin/JS
5.1.1. Gradle DSL 更改

kotlin.jsmultiplatformGradle插件中,引入了新的重要设置。在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项目的依赖项。

5.1.2. 新后端

Kotlin 1.4-M1是第一个包含针对Kotlin / JS目标的新IR编译器后端的版本。此后端是极大改进的基础,也是Kotlin / JS与JavaScript和TypeScript交互方式发生某些变化的决定性因素。以下突出显示的几个功能均针对新的IR编译器后端。虽然默认情况下尚未启用它,我们鼓励你在项目中尝试以下它。

(1)如何使用新的后端?

gradle.properties配置文件中添加以下配置:

kotlin.js.compiler=ir // or both

如果需要为IR编译器后端和默认后端生成库,则可以选择将此标志设置为both

关于both 的作用请看下面的章节介绍。

(2)无二进制兼容

新的IR编译器后端与原来默认的后端相比主要的变换是没有二进制兼容,Kotlin / JS的两个后端之间缺乏这种兼容性,这意味着使用新的IR编译器后端创建的库无法从默认后端使用,反之亦然。

(3)DCE 优化

与默认后端相比,新的IR编译器后端进行了很多优化。生成的代码与静态分析器配合使用效果更好了,甚至可以通过Google的Closure Compiler从新的IR编译器后端运行生成的代码,并使用其高级优化模式。

(4)支持声明导出到JavaScript

现在,标记为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!
(5)支持TypeScript定义

新的编译器支持从Kotlin代码生成TypeScript定义,对于配置produceExecutable()配置项,并且使用了上面的@JsExport的顶级声明,将生成带有TypeScript定义的.d.ts文件。如上面的代码,生成的文件如下所示:

// [...]
namespace blogpost {
    class KotlinGreeter {
        constructor(who: string)
        greet(): string
    }
    function farewell(who: string): string
}
// [...]
6. Kotlin/Native的一些变更
6.1. Objective-C默认支持泛型

Kotlin的早期版本为Objective-C互操作中的泛型提供了实验性支持。要从Kotlin代码生成具有泛型的框架头,必须使用-Xobjc-generics选项。在1.4-M1中,默认就支持范型了。但在某些情况下,这可能会破坏现有的调用Kotlin框架的Objective-C或Swift代码。如果不想使用范型,请添加-Xno-objc-generics选项

binaries.framework {
     freeCompilerArgs += "-Xno-objc-generics"
}
6.2. Objective-C/Swift 互操作中异常处理变化

在1.4中,我们略微更改了从Kotlin生成Swift API异常处理的方式。Kotlin和Swift的错误处理存在根本的不同,所有Kotlin异常均未经检查,而Swift仅检查错误。因此,为了使Swift代码感知异常,需使用@Throws注解标记Kotlin函数,该注解指定潜在异常类的列表。

当编译为Swift或Objective-C框架时,具有或正在继承@Throws注解的函数在Objective-C中表示为NSError *处理方法,而在Swift中表示为throws方法。

6.3. 性能提升

我们一直在努力提高Kotlin / Native编译和执行的整体性能。1.4-M1中,我们为提供了新的对象分配器,在某些基准测试中,它的运行速度提高了两倍。当前,新的分配器是实验性的,默认情况下不使用。您可以使用-Xallocator = mimalloc切换至该选项。

7.总结

以上就是Kotlin1.4-M1的一些变化,其中最令我惊喜的一个功能是:终于支持Kotlin interface SAM 转换了。其他的一些功能大家都可以去试一下,更多更详细的信息请去官网了解,期待早点出release版吧!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容