闭门造车姚师傅

Kotlin 1.3 都更新了些啥

字数统计: 1.3k阅读时长: 5 min
2018/11/22 Share

Kotlin 1.3 release 到现在已经一个多月了,除了协程正式 release 之外,还是有些别的惊喜的。比如 contract, 无符号整型, inline class 以及各种各样的语法糖,回顾一下。

协程 正式 release 了

这个一言难尽,直接左转吧:http://kotlinlang.org/docs/reference/coroutines-overview.html

Contracts | 契约*

Kotlin 编译器进行拓展静态分析来提供警告并且避免模板代码。 最有意义的一个 feature 是 智能转型——针对既有的类型检查来自动进行类型转换。

如果写过 Java 的话,对下面这段代码应该不是很陌生:这个强制类型转换看起来就很“多余”。

1
2
3
4
Object obj = foo();  // public static Object foo() { return "Boom!"; }
if (obj instanceof String) {
System.out.println("obj is String, and it's length is " + ((String) obj).length());
}

再看这段代码(对于 Kotlin 的类型系统,String?String 是两个不同的类型),这里展示了智能转型的语法糖:

1
2
3
fun foo(s: String?) {
if (s != null) s.length // `s` smartcasted to `String`
}

但是,这个检查比较粗糙(或者说由于性能等方面的考量,难以做得细致),一旦我们把这些检查放到单独的函数,智能转型就会失效:

1
2
3
4
5
6
7
fun foo(str: String?) {
if (str.isNotNull()) {
str.length // not allowed
}
}

fun String?.isNotNull() = this != null

所以,引入了 contract,这样重写 String?.isNotNull() 就好了:

1
2
3
4
5
6
7
@ExperimentalContracts
fun String?.isNotNull(): Boolean {
contract {
returns(true) implies(this@isNotNull != null)
}
return this != null
}

标准库中的 contract

Kotlin stdlib 已经在使用 contract 了,比如这种(Strings.kt:218):

1
2
3
4
5
6
7
8
9
10
11
/**
* Returns `true` if this nullable char sequence is either `null` or empty.
*/
@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
contract {
returns(false) implies (this@isNullOrEmpty != null)
}

return this == null || this.length == 0
}

自定义 contract

现在 contract 还是 Experimental 的,编译器并不会检查这些契约,甚至 contract 的格式/语法还极大可能发生变化。

就编程风格来说,一般有 defensive(检查参数,甚至返回默认值) 和 aggressive(反手就是一个 IllegalArgumentException) 两个流派,这个没有是非对错之分,更多的只是在某种情形下执行某种实践(甚至很多团队对此没有统一的 约束)。所以感觉 contract 这个东西还是太理想化了,毒奶一口,目测会 deprecated。

自定义 contract 就是在 contract DSL 里来这么一句:returns xx implies xx (如果返回值是啥啥啥,意味着啥啥啥)。 下面是一个例子:

1
2
3
4
5
@ExperimentalContracts
fun String?.isNullOrEmpty(): Boolean {
contract { returns(false) implies (this@isNullOrEmpty != null) }
return this == null || isEmpty()
}

捕获 when 的参数作为变量

针对使用 when 的某一特殊场景,可以少些几行代码。可以这么写:

1
2
3
4
5
when (val data = fetchData()) {
is Number -> {
data.toDouble().apply(::println)
}
}

而不是这么写:

1
2
3
4
5
6
7
fetchData().also { data ->
when (data) {
is Number -> {
data.toDouble().apply(::println)
}
}
}

在接口的伴生对象中使用 @JvmStatic@JvmField 注解

可以这么写了:

1
2
3
4
5
6
7
8
9
interface Foo {
companion object {
@JvmField val answer: Int = 42

@JvmStatic fun sayHello() {
println("Hello, world!")
}
}
}

等价的 Java 形式:

1
2
3
4
5
6
interface Foo {
public static int answer = 42;
public static void sayHello() {
// ...
}
}

注解类的嵌套声明

1
2
3
4
5
6
7
annotation class Foo {
enum class DIRECTION { UP, DOWN }
annotation class Bar
companion object {
val bar = 12
}
}

无参的 main 方法

嗯,一下子少了 19 个字符

1
fun main() { /* Yeah! */ }

参数巨多的 Function

Function0<R> , Function1<P0, R> , Function2<P0, P1, R> , 之前是到 Function22 . Kotlin 1.3 中引入了 FunctionN, 以可变参数(或者说数组)的形式进行扩充。

Before:

1
fun naiveKotlin(block: (Any, Any, Any) -> Any) { }
1
2
3
4
// #1
MainKt.naiveKotlin((o, o2, o3) -> null);
// #2
MainKt.naiveKotlin(new Function3<Object, Object, Object, Object>() { /* */ });

After:

1
2
3
4
5
6
7
8
9
fun trueEnterpriseComesToKotlin(block: (Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
Any, Any, Any, Any, Any, Any, Any, Any, Any, Any,
Any, Any, Any, Any, Any, Any, Any, Any, Any, Any) -> Any) {

}
1
MainKt.trueEnterpriseComesToKotlin(new FunctionN<Object>() {});

内联类

1
inline class Name(val s: String)

内联类是对普通的类施加了很多限制以便于进行各种各样的优化。

内联类只能有一个字段(属性)。

@JvmDefault 注解*

1
2
3
interface Foo {
@JvmDefault fun foo() = 233
}

会编译为 Java 的接口默认方法(since JDK8),因此可能会对代码的可移植性造成不好的影响

标准库的变化

数组间元素复制

更 Kotlin 了

1
2
3
4
5
6
7
8
9
val sourceArr = arrayOf("k", "o", "t", "l", "i", "n")

// last 3 elements
val targetArr = sourceArr.copyInto(arrayOfNulls<String>(6), 3, startIndex = 3, endIndex = 6)
println(targetArr.contentToString())

// first 3 elements
sourceArr.copyInto(targetArr, startIndex = 0, endIndex = 3)
println(targetArr.contentToString())

associateWith

ListMap 的变换,现在可以这样写了:

1
2
3
List(5) { idx -> 'a' + idx }
.associateWith { it.toString().repeat(5).capitalize() }
.forEach(::println)

之前要这样写:

1
2
3
List(5) { idx -> 'a' + idx }
.associate { it to it.toString().repeat(5).capitalize() }
.forEach(::println)

都会产生相同的输出:

1
2
3
4
5
a=Aaaaa
b=Bbbbb
c=Ccccc
d=Ddddd
e=Eeeee

其他细微更改

多平台的 Random

isNullOrEmpty/orEmpty 拓展函数

ifEmpty & ifBlank

反射中的 sealed class


此外,还有这些:

  • 无符号整数
  • Progressive Mode
  • Coroutines Release
  • Kotlin/Native
  • Multiplatform Projects
  • 代码风格 IDE 支持
  • kotlinx.serialization
  • Kotlin Script
  • Kotlin Scratch

http://kotlinlang.org/docs/reference/whatsnew13.html

CATALOG
  1. 1. 协程 正式 release 了
  2. 2. Contracts | 契约*
    1. 2.1. 标准库中的 contract
    2. 2.2. 自定义 contract
  3. 3. 捕获 when 的参数作为变量
  4. 4. 在接口的伴生对象中使用 @JvmStatic 和 @JvmField 注解
  5. 5. 注解类的嵌套声明
  6. 6. 无参的 main 方法
  7. 7. 参数巨多的 Function
  8. 8. 内联类
  9. 9. @JvmDefault 注解*
  10. 10. 标准库的变化
    1. 10.1. 数组间元素复制
    2. 10.2. associateWith
    3. 10.3. 其他细微更改
      1. 10.3.1. 多平台的 Random
      2. 10.3.2. isNullOrEmpty/orEmpty 拓展函数
      3. 10.3.3. ifEmpty & ifBlank
      4. 10.3.4. 反射中的 sealed class