历经了漫长而充足的的测试,协程终于正式发布了!这意味着自 Kotlin 1.3 起,协程的语言支持与 API 已完全稳定。参见新版协程概述。
Kotlin 1.3 引入了挂起函数的可调用引用以及在反射 API 中对协程的支持。
Kotlin 1.3 继续改进与完善原生平台。详情请参见 Kotlin/Native 概述。
在 1.3 中,我们完全修改了多平台项目的模型,以提高表现力与灵活性,并使共享公共代码更加容易。此外,多平台项目现在也支持 Kotlin/Native!
与旧版模型的主要区别在于:
expectedBy
更多相关信息,请参考多平台程序设计文档。
Kotlin 编译器会做大量的静态分析工作,以提供警告并减少模版代码。其中最显著的特性之一就是智能转换——能够根据类型检测自动转换类型。
fun foo(s: String?) { if (s != null) s.length // 编译器自动将“s”转换为“String” }
然而,一旦将这些检测提取到单独的函数中,所有智能转换都立即消失了:
fun String?.isNotNull(): Boolean = this != null fun foo(s: String?) { if (s.isNotNull()) s.length // 没有智能转换 :( }
为了改善在此类场景中的行为,Kotlin 1.3 引入了称为 契约 的实验性机制。
契约 让一个函数能够以编译器理解的方式显式描述其行为。目前支持两大类场景:
fun require(condition: Boolean) { // 这是一种语法格式,告诉编译器: // “如果这个函数成功返回,那么传入的‘condition’为 true” contract { returns() implies condition } if (!condition) throw IllegalArgumentException(...) } fun foo(s: String?) { require(s is String) // s 在这里智能转换为“String”,因为否则 // “require”会抛出异常 }
fun synchronize(lock: Any?, block: () -> Unit) { // 告诉编译器: // “这个函数会在此时此处调用‘block’,并且刚好只调用一次” contract { callsInPlace(block, EXACTLY_ONCE) } } fun foo() { val x: Int synchronize(lock) { x = 42 // 编译器知道传给“synchronize”的 lambda 表达式刚好 // 只调了一次,因此不会报重复赋值错 } println(x) // 编译器知道一定会调用该 lambda 表达式而执行 // 初始化操作,因此可以认为“x”在这里已初始化 }
stdlib(kotlin 标准库)已经利用契约带来了如上所述的对代码分析的改进。这部分契约是稳定版的,这意味着你现在就可以从改进的代码分析中受益,而无需任何额外的 opt-ins:
stdlib
//sampleStart fun bar(x: String?) { if (!x.isNullOrEmpty()) { println("length of '$x' is ${x.length}") // 哇,已经智能转换为非空! } } //sampleEnd fun main() { bar(null) bar("42") }
可以为自己的函数声明契约,不过这个特性是实验性的,因为目前的语法尚处于早期原型状态,并且很可能还会更改。另外还要注意,目前 Kotlin 编译器并不会验证契约,因此程序员有责任编写正确合理的契约。
通过调用标准库(stdlib)函数 contract 来引入自定义契约,该函数提供了 DSL 作用域:
contract
fun String?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty != null) } return this == null || isEmpty() }
请参见 KEEP 中关于语法与兼容性注意事项的详细信息。
when
在 Kotlin 1.3 中,可以将 when 表达式主语捕获到变量中:
fun Request.getBody() = when (val response = executeRequest()) { is Success -> response.body is HttpError -> throw HttpException(response.status) }
虽然已经可以在 when 表达式前面提取这个变量,但是在 when 中的 val 使其作用域刚好限制在 when 主体中,从而防止命名空间污染。关于 when 表达式的完整文档请参见这里。
val
对于 Kotlin 1.3,可以使用注解 @JvmStatic 与 @JvmField 标记接口的 companion 对象成员。在类文件中会将这些成员提升到相应接口中并标记为 static。
@JvmStatic
@JvmField
companion
static
例如,以下 Kotlin 代码:
interface Foo { companion object { @JvmField val answer: Int = 42 @JvmStatic fun sayHello() { println("Hello, world!") } } }
相当于这段 Java 代码:
interface Foo { public static int answer = 42; public static void sayHello() { // …… } }
在 Kotlin 1.3 中,注解可以有内嵌的类、接口、对象与伴生对象:
annotation class Foo { enum class Direction { UP, DOWN, LEFT, RIGHT } annotation class Bar companion object { fun foo(): Int = 42 val bar: Int = 42 } }
main
按照惯例,Kotlin 程序的入口点是一个签名类似于 main(args: Array<String>) 的函数,其中 args 表示传给该程序的命令行参数。然而,并非每个应用程序都支持命令行参数,因此这个参数往往到最后都没有用到。
main(args: Array<String>)
args
Kotlin 1.3 引入了一种更简单的无参 main 形式。现在 Kotlin 版的 Hello,World 缩短了19个字符!
Hello,World
fun main() { println("Hello, world!") }
在 Kotlin 中用带有不同数量参数的泛型类来表示函数类型:Function0<R>、 Function1<P0, R>、 Function2<P0, P1, R>……这种方式有一个问题是这个列表是有限的,目前只到 Function22。
Function0<R>
Function1<P0, R>
Function2<P0, P1, R>
Function22
Kotlin 1.3 放宽了这一限制,并添加了对具有更多元数(参数个数)的函数的支持:
fun trueEnterpriseComesToKotlin(block: (Any, Any, …… /* 42 个 */, Any) -> Any) { block(Any(), Any(), ……, Any()) }
Kotlin 非常注重代码的稳定性与向后兼容性:Kotlin 兼容性策略提到“破坏性变更”(例如,会使以前编译正常的代码现在不能通过编译的变更)只能在主版本(1.2、1.3 等)中引入。
我们相信很多用户可以使用更快的周期,其中关键的编译器修复会即时生效,从而使代码更安全、更正确。因此 Kotlin 1.3 引入了渐进编译器模式,可以通过将参数 -progressive 传给编译器来启用。
-progressive
在渐进模式下,语言语义中的一些修复可以即时生效。所有这些修复都有以下两个重要特征:
启用渐进模式可能需要重写一些代码,但不会太多——所有在渐进模式启用的修复都已经过精心挑选、通过审核并提供迁移辅助工具。 我们希望对于任何积极维护、即将快速更新到最新语言版本的代码库来说,渐进模式都是一个不错的选择。
内联类从 Kotlin 1.3 开始支持,而且现阶段是实验性的。更多详情请看这里。 {:.note}
Kotlin 1.3 引入了一种新的声明方式 — 内联类。内联类可视为常规类的限制版,值得一提的是,内联类必须有一个确切的属性:
内联类
inline class Name(val s: String)
Kotlin 编译器将会利用这个限制,积极的提升内联类在运行时的表现,并且使用底层的属性值来替换内联类的实例,用于省略构造方法调用,减小 GC 压力,开启其他优化操作:
inline class Name(val s: String) //sampleStart fun main() { // 下一行不会调用构造方法 // 而且在运行时,'name' 只会包含字符串 "Kotlin" val name = Name("Kotlin") println(name.s) } //sampleEnd
点击这里查看内联类更多细节。
无符号整型从 Kotlin 1.3 开始支持,而且现阶段是实验性的。更多详情请看这里。 {:.note}
Kotlin 1.3 引入了无符号整型类型:
kotlin.UByte
kotlin.UShort
kotlin.UInt
kotlin.ULong
无符号类型也支持大多数有符号类型的功能:
fun main() { //sampleStart // 你可以使用字面前缀定义无符号类型: val uint = 42u val ulong = 42uL val ubyte: UByte = 255u // 你可以把有符号类型通过标准库向无符号类型转换,反之亦然: val int = uint.toInt() val byte = ubyte.toByte() val ulong2 = byte.toULong() // 无符号类型支持同义操作符: val x = 20u + 22u val y = 1u shl 8 val z = "128".toUByte() val range = 1u..5u //sampleEnd println("ubyte: $ubyte, byte: $byte, ulong2: $ulong2") println("x: $x, y: $y, z: $z, range: $range") }
点击这里查看无符号整型更多细节。
@JvmDefault 从 Kotlin 1.3 开始支持,而且现阶段是实验性的。更多详情请看这里。 {:.note}
@JvmDefault
Kotlin 兼容很多 Java 版本,包含接口中不允许方法默认实现的 Java6 和 Java7 。方便起见,Kotlin 编译器可以解决这个问题,但是这个解决方案将不会和 Java8 中的 default 方法兼容。
default
对于和 Java 的交互,这可能是个问题,所以 Kotlin 1.3 引入了 @JvmDefalut 注解。被该注解标记的方法将会被 JVM 编译为 default 方法。
@JvmDefalut
interface Foo { // 将会被编译为默认方法 @JvmDefault fun foo(): Int = 42 }
注意! 将你的 API 标记为 @JvmDefault 将会对二进制兼容性有严重影响。请确保你仔细阅读了参考页面,在你的产品中使用@Default之前。 {:.note}
@Default
在 Kotlin 1.3 之前,没有办法在所有平台上统一来生成随机数 — 我们只能采取依赖平台的特定解决方案,比如 JVM 上的 java.util.Random。这个版本将会解决这个问题,通过引入 kotlin.random.Random,这是个支持所有平台的类:
java.util.Random
kotlin.random.Random
import kotlin.random.Random fun main() { //sampleStart val number = Random.nextInt(42) // 数字区间 [0, limit) println(number) //sampleEnd }
某些类型的 isNullOrEmpty 和 orEmpty 扩展函数已经存在于标准库中,对于前者,如果函数接受者是 null或者为空将会返回 true ;对于后者, 如果接收者是 null ,将会返回一个空实例。 Kotlin 1.3 为 collections、maps 和对象数组提供了类似的扩展函数。
isNullOrEmpty
orEmpty
null
true
collections
maps
对于非空数组类型,包括无符号数组类型,函数 array.copyInto(targetArray, targetOffset, startIndex, endIndex) 使得数组拷贝在 Kotlin 中更加简单。
array.copyInto(targetArray, targetOffset, startIndex, endIndex)
fun main() { //sampleStart val sourceArr = arrayOf("k", "o", "t", "l", "i", "n") val targetArr = sourceArr.copyInto(arrayOfNulls<String>(6), 3, startIndex = 3, endIndex = 6) println(targetArr.contentToString()) sourceArr.copyInto(targetArr, startIndex = 0, endIndex = 3) println(targetArr.contentToString()) //sampleEnd }
对于一个 list 一个非常常见的场景是,希望通过把 list 的每个元素作为 key,然后和某个 value 关联起来,来构建一个 map。这可以通过 associate { it to getValue(it) } 函数来完成,但是现在我们引入了一个更为效率和便捷的实现方式:keys.associateWith { getValue(it) }。
associate { it to getValue(it) }
keys.associateWith { getValue(it) }
fun main() { //sampleStart val keys = 'a'..'f' val map = keys.associateWith { it.toString().repeat(5).capitalize() } map.forEach { println(it) } //sampleEnd }
Collections, maps, object arrays, char sequence 和 sequence 现在都有 ifEmpty 函数,用于指定一个默认值,当接收者为空时:
ifEmpty
fun main() { //sampleStart fun printAllUppercase(data: List<String>) { val result = data .filter { it.all { c -> c.isUpperCase() } } .ifEmpty { listOf("<no uppercase>") } result.forEach { println(it) } } printAllUppercase(listOf("foo", "Bar")) printAllUppercase(listOf("FOO", "BAR")) //sampleEnd }
Char sequences 和 strings 还额外拥有 ifBlank 扩展,和 ifEmpty 功能类似,但是用于检查一个 string 是否全是空格。
ifBlank
fun main() { //sampleStart val s = " \n" println(s.ifBlank { "<blank>" }) println(s.ifBlank { null }) //sampleEnd }
我们向 kotlin-reflect 添加了一个新的 API : KClass.sealedSubclasses,用于列出所有密封类的子类型。
kotlin-reflect
KClass.sealedSubclasses
Boolean
Any?.hashCode()
Char
MIN_VALUE
MAX_VALUE
SIZE_BYTES
SIZE_BITS
Kotlin 1.3 在 IDE 中引入了代码推荐风格支持。具体迁移指南查看这里。
kotlinx.serialization 是一个提供支持多平台的序列化/反序列化的库。之前作为一个独立的库,从 Kotlin 1.3 起,它将会和其他编译器插件一样,包含在 Kotlin 编译器发行版中。主要区别在于,你不在需要自行关注 IDE 的序列化插件和 Kotlin IDE 插件的兼容性问题:因为 Kotlin IDE 插件已经支持序列化!
具体详情查看这里。
值得注意的是,即便 kotlinx.serialization 包含在 Kotlin 编译器发行版中,它仍是一个实验性功能。 {:.note}
注意,脚本是个实验性功能,对于现有的 API, 并没有任何兼容性的保障。 {:.note}
Kotlin 1.3 将继续完善 script API,引入了一些自定义脚本实验性的支持,比如添加额外属性,提供静态或者动态依赖等。
更详细的细节,请参考KEEP-75。
Kotlin 1.3 引入了对可运行的草稿文件支持。草稿文件是一个以 .kts 扩展名结尾的 kotlin 脚本文件,你可以在编辑器里直接运行和获取计算结果。
更多细节请关注Scratches documentation。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8