Kotlin 1.3 RC 来啦:快迁移您的协程代码吧!

2,403 阅读11分钟
原文链接: www.kotliner.cn

现在我们非常自豪地发布 Kotlin 1.3-RC,这将是 1.3 之前的最后一个预览版本。这个版本修复了绝大多数的新功能相关的关键 bug,除此之外,还有一些其他的更新值得一提:

  • Ktor 的 API 已经完善,期待大家的使用体验和反馈。Ktor 是一个基于协程的框架,使用它我们可以优雅的构建 Web 应用、Http 服务、移动应用和网页。
  • 所有的新项目都已经采用官方代码规范 ,我们也非常鼓励大家也这样做。
  • 程序入口 main 函数支持无参数的形式,它也可以是一个 suspend 函数。
  • 1.2.50 引进的对脚本的支持如今也得到了更新和完善。
  •  kotlinx.serialization 的插件已经合入 Kotlin 主工程,并且被集成到编译器插件当中。
  • 我们废弃了一些标准库当中的 API,当然对于过去就已经废弃的 API 这次我们直接报错。
  •  org.jetbrains.annotations 当中的注解从标准库中独立出来,成为一个新的构件方便开发者自由选择依赖。

RC

感谢来自社区的贡献者们,这个版本合入了以下开发者的代码提交:Toshiaki KameyamaCuihtlauac ALVARADOkenji tomitaMartin PetrovDenis VnukovshirajiPaul de VriezeRaluca SauciucIvan Gavrilovic.

完整的更新内容请参见 更新日志

如果您在试用 1.3 版本的新功能和特性时遇到了问题,请及时与我们联系,我们非常期待和感谢您的反馈。Kotlin 1.3.0 的最终版本即将到来,请抓紧时间分享您的使用体验吧!

2018 Kotlin 开发者大会

Kotlin 开发者大会即将召开!对于不方便现场参加大会的开发者,我们将提供现场直播以及资料共享。请您 注册 以便得到我们的直播通知。

是时候迁移您的协程代码了

从 Kotlin 1.3 开始,协程将进入稳定状态。鉴于此,大家可以在 1.3.0 发布之后就可以立刻马上迁移协程代码了,我们也会提供相应的工具帮助大家完成这项工作。

对于kotlinx.coroutines 来说:

  1. 您需要在实验状态下(experimental) 更新到最新的版本,当前最新版是 0.26.1(Kotlin 1.3 发布之前也可能会有少量的更新)
  2. 将您的项目直接切换到 Kotlin 1.3,IDE 的工具会帮助您完成迁移,目前来说就是切换到 0.26.1-eap13,这个版本是基于 Kotlin 1.3 构建的。这个迁移工作主要是去掉包名当中的 experimental。
  3. 待 Kotlin 1.3 正式发版之后,kotlinx.coroutines也将会发布 1.0 版,届时您只需要更新版本号,重新编译代码即可。

需要注意的是,kotlinx.coroutines 1.0 将会丢弃所有之前的 0.x 版本中的废弃的接口和声明,与 0.x-eap13 版本二进制不兼容,因此在切换正式版之前就需要把您代码中使用过时 API 的代码进行整理。

kotlinx.coroutines也会有一些 API 被标记为“unstable”,它们将会在后续的更新中得到完善。尽管使用这些“unstable”的 API 需要特定的选项配置,但核心 API 都将在 1.0 最终确定并保证如同 Kotlin 标准库一样在后续更新中后向兼容。

不需要参数的 “Main” 函数

就像其他很多语言那样,我们从 1.3 开始允许 “main” 函数的字符串数组参数可选,如此一来,经典的 “Hello, World!” 在 Kotlin 当中就更短啦:

fun main() {
    println("Hello, World!")
}

可挂起的 “main” 函数

“main”函数不仅参数可选,还可以是 “suspend”,换句话说,您再也不需要使用 “runBlocking” 就可以在 “main”函数当中直接调用其他 “suspend” 函数了:

import kotlinx.coroutines.*

//sampleStart
suspend fun main() = coroutineScope {
    val task1 = async { fetchResult(id = 42) }
    val task2 = async { fetchResult(id = 99) }

    val results = awaitAll(task1, task2)

    log(results)
}
//sampleEnd

suspend fun fetchResult(id: Int): String {
    log("started request #$id")
    delay(500)
    return "result #$id"
}

val startedAt = System.currentTimeMillis()
fun elapsed() = System.currentTimeMillis() - startedAt
fun log(message: Any) = println("At ${elapsed()} ms: $message")

Ktor 框架

协程终于“毕业”,所以妈妈再也不用担心我们发布我们自己的异步框架了。为此,我们做了很多工作,其中最值得一提的是 Ktor –
一个用于连接应用的异步框架。它包括一个跨平台的 HTTP 客户端和一个基于 JVM 的 HTTP 服务端。

客户端目前已经支持 JVM/Android 和 iOS,后续也将支持更多的平台包括 JS(浏览器)和各种各样的 native 平台。服务端则运行在 JVM 上面,您可以使用 Netty、Jetty 以及其他类似于 Tomcat 的 servlet 容器。

Ktor 测试充分、易于上手、性能优越,请参考 ktor.io/。它的 0.9.5-rc13 兼容 Kotlin 1.3-RC,0.9.5 则兼容 Kotlin 1.2.70。

与我们一起打造我们自己的异步框架吧,这样我们就可以让我们的代码可读性和可维护性得到飞跃!

编码规范

官方代码规范文档已经推出了一些时日,是时候做进一步的工作了:从 Kotlin 1.3 开始,使用 IntelliJ IDEA 创建的所有新项目都将应用这份代码规范。

虽然我们建议大家通过提交 .idea/codeStyles 到 VCS 来共享代码风格,但不少开发者的内心却是拒绝的。因此我们引入了一个 Gradle 的属性 kotlin.code.style,可以设置值为 official 或 obsolete。如此一来,其他 IDE 也就可以享受到这一特性了。

如果您想要使用原来的代码风格,只需要设置 kotlin.code.style=obsolete以免后续遇到迁移上的麻烦。这个属性在 Maven 当中也适用。

脚本

这个版本对脚本的支持做了很多优化改进,其中最重要的就是重新打造的更简洁更清爽的 API。尽管脚本支持的特性可能还需要一段时间的实验,但目前看来 API 将基本定型。其中的一些无用的和尚未实现的 API 被移除,等时机成熟,在后续的版本当中我们有意对绝大多数移除的 API 进行召回。

为了简化脚本项目的构建和工具脚本的依赖,我们提供了一个新的组件 kotlin-main-kts。这个简单的 jar 包提供了一些脚本的定义和基本的 Maven 构建解析器,有了它,我们就可以写出下面的脚本了:

@file:Repository("https://jcenter.bintray.com")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.11")

import kotlinx.html.*
import kotlinx.html.stream.*

print(createHTML().html {
    body {
        h1 { +"Hello, World!" }
    }
})

运行时只需要将 kotlin-main-kts.jar 添加到 classpath:

kotlinc -cp <path/to/kotlin-main-kts.jar> -script sample.main.kts

脚本支持方面还有其他的许多问题修复和功能提升,建议大家通过案例多多尝试,并积极反馈~

完整的 Kotlin 脚本提案以及当前的实现状态可以参考 KEEP-75

实验状态的序列化插件

从这个版本开始,以前独立的插件 kotlinx.serialization 将合并到 Kotlin 的编译器插件当中。

尽管在 Kotlin 1.3 当中它将仍然保持实验状态,但这并不影响它在生产环境中的使用,它是足够成熟和稳定的,我们也非常期待大家的反馈。相关提案请参见:KEEP-149,如果有什么意见和建议,欢迎开 issue 讨论。

内联类 Inline classes

我们调整了内联类的二进制表示,目的是让它更加紧凑、更与时俱进,同时也修复了一些关键的问题。Kotlin 反射也可以正确的处理内联类,并完成自动的拆箱和装箱。

标准库

相比之下,这个版本更关注正式发版前的代码整理而不是新功能。

可空类型的 hashCode()

当实现一个泛型容器类时,通常我们需要获取元素的哈希值,而元素通常也可以为 null,因此过去我们需要使用 ?. 以及 elvis 运算符来解决这个问题:

element?.hashCode() ?: 0.

现在我们有了 Any?.hashCode(),对于任意元素无论是否为空,都可以如此调用: element.hashCode()

Range 迭代时的元素空检查

value 是可空类型,同时 Range 是 Iterable且范围较大时,在 value in from..to 中进行空检查将非常耗时。这种情况下,范围的包含关系判断会退化成 Iterable<T>.contains(T),这样整个过程就成了 Iterable 的遍历(直到元素找到或者确定找不到为止)。

为了解决这个问题,我们引入了一系列 contains 的重载,以 IntRange.contains(element: Int?) 为例,它会先检查参数 element 是否为空,紧接着执行一个快速的包含关系判断:

class IntRange(start: Int, endInclusive: Int) : IntProgression(start, endInclusive, 1), ClosedRange<Int> {
    ...
    override fun contains(value: Int): Boolean = first ≤ value && value ≤ last
    ...
}

@SinceKotlin("1.3")
public inline operator fun IntRange.contains(element: Int?): Boolean {
    return element != null && contains(element)
}

SuccessOrFailure 更名为 Result

我们在之前的 Kotlin 1.3 预览版中提供了一个封装 Kotlin 函数结果的类 SuccessOrFailure,经过 review 和社区反馈,更名为Result。这个类目前主要应用于协程,作为Continuation.resumeWith的参数。Kotlin 中有许多设计线索可用于将来扩展错误处理,这将要求我们重新设计使用此Result类作为返回类型的代码的语义。因此,为避免将来的设计会破坏此类代码,Kotlin 1.3 编译器会对于这类声明报错,当然标准库函数的一些情况则是例外,因为这些函数专门用于对 Result 类型进行操作。详细请参见 KEEP-127

Sequence 和 iterator 的创建函数以及他们的 scope 类

在协程 API 最终确定下来之前,我们需要对 buildSequence 和 buildIterator 进行重命名。这些名字主要是为了与  buildString 以及一些构造集合框架类型的函数提议保持一致。然而,我们最近意识到 sequence 和 string 的创建函数在懒加载方面差异较大,所谓的一致性实际上并不是很合理。

这些创建函数从 buildSequence { } 和 buildIterator { } 更名为 sequence { } 和 iterator { }。参数的 receiver 类则从 SequenceBuilder 变更为 SequenceScope

这样一来,Sequence 的创建代码将变成:

import kotlinx.coroutines.*

//sampleStart
fun idCandidates(baseId: String): Sequence<String> = sequence {
    yield(baseId)
    for (suffix in 'a'..'z') {
        yield(baseId + "-" + suffix)
    }
}
//sampleEnd

fun main() {
    idCandidates("base-id").take(10).forEach(::println)
}

有符号整型转无符号时符号位扩展到长整型

整型有符号到无符号转换的处理方法与其他语言趋于一致,Int.toULong() 以及其他类似的函数在转换时采用了符号位扩展到高位的做法,而不是 0 扩展,所以 (-1).toULong() 现在等于 ULong.MAX_VALUE (0xFFFF_FFFF_FFFF_FFFF) 而不是 UInt.MAX_VALUE (0x0000_0000_FFFF_FFFF).

注解成为独立的构件

与编译器一起发布的标准库不再包含 org.jetbrains.annotations 中的注解,这样也可以与 Maven Central 中发布的标准库保持一致。这些注解现可以在 annotations-13.0.jar 和 mutability-annotations-compat.jar 当中找到。

废弃整型、浮点型混合的区间包含运算符

前面我们提到对于可空类型的包含关系判断会有问题,其实还有一个麻烦就是整型区间遇到浮点型参数这样类似的情况。例如 3.14 in 1..3 是可以编译运行的,但结果就有点儿尴尬了: true。我们废弃了这样的 contains 运算符重载,后面会逐渐移除它们。

其他废弃的通知

对于过去就已经废弃的 API,这次我们会提高它们的废弃级别,对于使用这些 API 的代码,我们会直接报错而不是警告。

这版要对 JS 标准库中曾经废弃的 API 下手的主要有:

  • jsClass函数
  • kotlin.Synchronized 和 Volatile 注解
  • kotlin.js.Math 函数
  • kotlin-test-js 当中 org.junit.Test 注解

如果这些您都不熟,那没关系。否则的话,请尽快按照说明迁移您的代码。

在下一个版本也就是 1.4 当中,我们将要移除标准库当中的 jQuery 类型的声明,相应的功能将由 https://bintray.com/kotlin/js-externals/kotlin-js-jquery 提供。

命令行编译器

这一版本支持我们选用另外的一种方式来通过文件给编译器传参。除了使用 1.2.50 中引入的 -Xargfile=filename.txt,您也可以直接给 kotlinc 传入 @filename.txt 这样的参数,文件内容将会被按行解析为参数。这样将会方便使用 kotlinc 编译大量的源文件,同时也可以方便处理更长更复杂的文件路径。

这版中另一个要“毕业的”的实验特性便是 1.2.50 引入的 progressive 编译模式,我们可以通过 -progressive 来打开这种模式。

如何尝鲜

在 Maven/Gradle 项目中: 为构建脚本和项目依赖添加 repository https://dl.bintray.com/kotlin/kotlin-eap。使用 1.3.0-rc-57 作为编译器和标准库的版本号。

在 IntelliJ IDEA 中: 在 Tools → Kotlin → Configure Kotlin Plugin Updates中,Update channel 中选择 “Early Access Preview 1.3”,然后点击Check for updates

命令行编译器可以在 Github release page 下载。

 try.kotlinlang.org上: 在右下角的下拉列表中选择编译器的版本为 1.3‑RC 即可。

本文翻译自官方博客:Kotlin 1.3 RC is Here: Migrate Your Coroutines!