13.6.12 Kotlin 1.2 的新变化
10 分钟阅读
13.6.12 Kotlin 1.2 的新变化
阅读 Kotlin 1.2 的发行说明,涵盖新的语言特性、Kotlin Multiplatform、JVM 和 JS 的更新,以及对 Gradle 和 Maven 构建工具的支持。
发布日期:2017 年 11 月 28 日
目录
提示: 关于 Kotlin 发布周期的信息,请参阅 Kotlin 发布流程。
多平台项目(实验性)
多平台项目是 Kotlin 1.2 中新的实验性特性,让你可以在 Kotlin 支持的目标平台之间复用代码 —— JVM、JavaScript,以及(未来的)Native。在多平台项目中,有三种模块:
- 公共模块包含与任何平台都无关的代码,以及平台相关 API 的无实现声明。
- 平台模块包含公共模块中平台相关声明在特定平台上的实现,以及其他平台相关代码。
- 普通模块面向特定平台,既可以是平台模块的依赖,也可以依赖平台模块。
当你为特定平台编译多平台项目时,会同时生成公共部分和平台特有部分的代码。
多平台项目支持的一个关键特性是:可以通过 expected 和 actual 声明来表达公共代码对平台特有部分的依赖。expected 声明指定一个 API(类、接口、注解、顶层声明等)。actual 声明要么是该 API 的平台相关实现,要么是指向外部库中该 API 现有实现的类型别名。下面是一个例子:
在公共代码中:
| |
在 JVM 平台代码中:
| |
关于构建多平台项目的细节和步骤,请参阅多平台编程文档。
其他语言特性
注解中的数组字面量
从 Kotlin 1.2 开始,注解的数组实参可以使用新的数组字面量语法传入,而不必使用 arrayOf 函数:
| |
数组字面量语法仅限于注解实参。
lateinit 顶层属性和局部变量
lateinit 修饰符现在可以用于顶层属性和局部变量。后者可用于这样的情况:作为构造器实参传给某个对象的 lambda 引用了另一个需要稍后才定义的对象:
| |
检查 lateinit 变量是否已初始化
现在你可以使用属性引用上的 isInitialized 检查 lateinit 变量是否已初始化:
| |
带默认函数式参数的内联函数
内联函数现在允许为其内联的函数式参数提供默认值:
| |
显式转换中的信息用于类型推断
Kotlin 编译器现在可以利用类型转换中的信息进行类型推断。如果你调用一个返回类型参数 T 的泛型方法,并把返回值转换为特定类型 Foo,编译器现在会理解此次调用中的 T 需要绑定为类型 Foo。
这对 Android 开发者尤其重要,因为编译器现在可以正确分析 Android API 级别 26 中的泛型 findViewById 调用:
| |
智能转换的改进
当变量由安全调用表达式赋值并被检查是否为 null 时,智能转换现在也会应用到安全调用的接收者上:
| |
此外,lambda 中的智能转换现在允许作用于只在 lambda 之前被修改的局部变量:
| |
支持把 ::foo 作为 this::foo 的简写
对 this 的成员的有界可调用引用现在可以省略显式接收者,写成 ::foo 而不是 this::foo。这也让可调用引用在引用外部接收者成员的 lambda 中更方便使用。
破坏性变更:try 块之后可靠的智能转换
此前,Kotlin 会把 try 块内部的赋值用于该块之后的智能转换,这可能破坏类型安全和空安全并导致运行时失败。本次发布修复了该问题,使智能转换更严格,但会破坏一些依赖此类智能转换的代码。
要切换到旧的智能转换行为,请把回退标志 -Xlegacy-smart-cast-after-try 作为编译器参数传入。它将在 Kotlin 1.3 中被弃用。
弃用:data 类覆盖 copy
当 data 类派生自一个已经有相同签名 copy 函数的类型时,为 data 类生成的 copy 实现会使用父类的默认值,导致违反直觉的行为;如果父类没有默认参数,则会在运行时失败。
导致 copy 冲突的继承在 Kotlin 1.2 中已被弃用并给出警告,在 Kotlin 1.3 中将成为错误。
弃用:枚举条目中的嵌套类型
由于初始化逻辑中的问题,在枚举条目内部定义非 inner class 的嵌套类型已被弃用。这在 Kotlin 1.2 中会产生警告,并将在 Kotlin 1.3 中成为错误。
弃用:vararg 使用单个命名实参
为了与注解中的数组字面量保持一致,以命名形式为 vararg 参数传入单个元素(foo(items = i))已被弃用。请配合相应的数组工厂函数使用展开运算符:
| |
在这种情况下去除多余数组创建有一项优化,可以避免性能下降。单实参形式在 Kotlin 1.2 中会产生警告,并将在 Kotlin 1.3 中移除。
弃用:继承 Throwable 的泛型类的内部类
继承自 Throwable 的泛型类型的内部类可能在抛出-捕获场景中违反类型安全,因此已被弃用:在 Kotlin 1.2 中给出警告,在 Kotlin 1.3 中成为错误。
弃用:修改只读属性的幕后字段
在自定义 getter 中通过 field = ... 修改只读属性的幕后字段已被弃用:在 Kotlin 1.2 中给出警告,在 Kotlin 1.3 中成为错误。
标准库
Kotlin 标准库产物与拆分包
Kotlin 标准库现在与 Java 9 模块系统完全兼容,而后者禁止拆分包(多个 jar 文件声明同一个包中的类)。为了支持这一点,引入了新的产物 kotlin-stdlib-jdk7 和 kotlin-stdlib-jdk8,它们取代了旧的 kotlin-stdlib-jre7 和 kotlin-stdlib-jre8。
从 Kotlin 的角度看,新产物中的声明在相同的包名下可见,但对 Java 而言包名不同。因此,切换到新产物不需要对源代码做任何修改。
为确保与新模块系统兼容而做的另一项修改是:从 kotlin-reflect 库中移除了 kotlin.reflect 包中已弃用的声明。如果你此前在使用它们,需要改用 kotlin.reflect.full 包中的声明,该包自 Kotlin 1.1 起受支持。
windowed、chunked、zipWithNext
针对 Iterable<T>、Sequence<T> 和 CharSequence 的新扩展覆盖了诸如缓冲或批处理(chunked)、滑动窗口与计算滑动平均(windowed)以及处理相邻元素对(zipWithNext)等用例:
| |
fill、replaceAll、shuffle/shuffled
新增了一组用于操作列表的扩展函数:用于 MutableList 的 fill、replaceAll 和 shuffle,以及用于只读 List 的 shuffled:
| |
kotlin-stdlib 中的数学运算
为满足长期以来的需求,Kotlin 1.2 添加了 JVM 与 JS 通用的数学运算 API kotlin.math,其中包含以下内容:
- 常量:
PI和E - 三角函数:
cos、sin、tan及其反函数:acos、asin、atan、atan2 - 双曲函数:
cosh、sinh、tanh及其反函数:acosh、asinh、atanh - 指数运算:
pow(扩展函数)、sqrt、hypot、exp、expm1 - 对数:
log、log2、log10、ln、ln1p - 舍入:
ceil、floor、truncate、round(四舍六入五成双)函数roundToInt、roundToLong(四舍五入到整数)扩展函数- 符号与绝对值:
abs和sign函数absoluteValue和sign扩展属性withSign扩展函数- 两个值的
max和min - 二进制表示:
ulp扩展属性nextUp、nextDown、nextTowards扩展函数toBits、toRawBits、Double.fromBits(这些位于kotlin包中)
同一组函数(但没有常量)也可用于 Float 实参。
BigInteger 和 BigDecimal 的运算符与转换
Kotlin 1.2 引入了一组函数,用于操作 BigInteger 和 BigDecimal,以及从其他数值类型创建它们。这些函数是:
- 用于
Int和Long的toBigInteger - 用于
Int、Long、Float、Double和BigInteger的toBigDecimal - 算术和位运算符函数:
- 二元运算符
+、-、*、/、%以及中缀函数and、or、xor、shl、shr - 一元运算符
-、++、--,以及函数inv
浮点数与位表示之间的转换
新增了用于在 Double 和 Float 与其位表示之间转换的函数:
toBits和toRawBits,对Double返回Long,对Float返回IntDouble.fromBits和Float.fromBits,用于从位表示创建浮点数
Regex 现在可序列化
kotlin.text.Regex 类已成为 Serializable,现在可以用在可序列化的层次结构中。
Closeable.use 在可用时调用 Throwable.addSuppressed
当在关闭资源的过程中(此前已抛出其他异常时)抛出异常,Closeable.use 函数会调用 Throwable.addSuppressed。
要启用该行为,你的依赖中需要有 kotlin-stdlib-jdk7。
JVM 后端
构造器调用的规范化
从 1.0 版本起,Kotlin 就支持带复杂控制流的表达式,例如 try-catch 表达式和内联函数调用。按照 Java 虚拟机规范,这类代码是合法的。遗憾的是,当这类表达式出现在构造器调用的实参中时,一些字节码处理工具无法很好地处理它们。
为缓解这类字节码处理工具使用者遇到的问题,我们添加了命令行编译器选项(-Xnormalize-constructor-calls=MODE),让编译器为这类构造生成更像 Java 的字节码。这里的 MODE 是以下之一:
disable(默认)—— 以与 Kotlin 1.0 和 1.1 相同的方式生成字节码。enable—— 为构造器调用生成类似 Java 的字节码。这可能改变类的加载和初始化顺序。preserve-class-initialization—— 为构造器调用生成类似 Java 的字节码,同时确保保持类的初始化顺序。这可能影响应用的总体性能;只有在多个类之间共享复杂状态并在类初始化时更新它时才使用它。
“手动”的变通办法是把带控制流的子表达式的值存入变量,而不是在调用实参中直接求值。这与 -Xnormalize-constructor-calls=enable 类似。
Java 默认方法调用
在 Kotlin 1.2 之前,以 JVM 1.6 为目标时,覆盖 Java 默认方法的接口成员会在父调用处产生警告:Super calls to Java default methods are deprecated in JVM target 1.6. Recompile with '-jvm-target 1.8'。在 Kotlin 1.2 中,这会改为错误,因此任何此类代码都必须以 JVM 目标 1.8 编译。
破坏性变更:平台类型上 x.equals(null) 行为一致
对映射到 Java 原始类型的平台类型(Int!、Boolean!、Short!、Long!、Float!、Double!、Char!)调用 x.equals(null) 时,如果 x 为 null,此前会错误地返回 true。从 Kotlin 1.2 开始,对平台类型的 null 值调用 x.equals(...) 会抛出 NPE(但 x == ... 不会)。
要恢复 1.2 之前的行为,请向编译器传入标志 -Xno-exception-on-explicit-equals-for-boxed-null。
破坏性变更:修复平台 null 通过内联扩展接收者逃逸的问题
在平台类型的 null 值上调用的内联扩展函数此前不会检查接收者是否为 null,从而允许 null 逃逸到其他代码中。Kotlin 1.2 在调用点强制进行该检查,如果接收者为 null 就抛出异常。
要切换到旧行为,请向编译器传入回退标志 -Xno-receiver-assertions。
JavaScript 后端
默认启用 TypedArrays 支持
用于把 Kotlin 原始数组(例如 IntArray、DoubleArray)转换为 JavaScript 类型化数组的 JS 类型化数组支持此前是可选启用的特性,现在已默认启用。
工具
把警告视为错误
编译器现在提供了把所有警告都视为错误的选项。请在命令行中使用 -Werror,或使用以下 Gradle 代码片段:
| |