kotlin-Null safety

原文
我翻译的一篇文章。

概览

在本篇文章,我们将要研究一下 Kotlin 语言内置的 null 安全特性。 Kotlin 提供了可推测的,本地处理的可空字段 - 不需要额外的支持库。

Maven 依赖

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.1.1</version>
</dependency>

空值和非空值引用类型

Kotlin 有两种可以被编译器解释的引用类型,能够在编译阶段告知程序员代码的正确性 - 空值和非空值

默认情况下, Kotlin认为都是非空值:

var a: String = "value"

assertEquals(a.length, 5)

我们不能把 null 赋值给 a, 如果你尝试赋值,编译器会报错。
如果我们想创建一个可以赋空值的变量时,需要在定义变量的时候在后面添加一个 ?

var b: String? = "value"

这时就可以合法的赋空值了

b = null

当我们想要访问 b 的时候,我们必须处理 null 的情况来避免编译错误,因为 Kotlin 知道这个变量可能为空

if (b != null) {
println(b.length)
} else {
assertNull(b)
}

Elvis操作符

有时,我们希望操作空值时能返回指定的默认值。 这时可以使用 elvis(?:) 操作符,对于 Java Optional class 来说相当于 orElse/orElseGet

val value: String? = null

val res = value?.length ?: -1

assertEquals(res, -1)

当引用值非空时, length 方法会被调用

val value: String? = "name"

val res = value?.length ?: -1

assertEquals(res, 4)

!! 操作符 强制转换

可以使用 !! 来人工保证不会是空值

val nullableInt: Int? = 1
val nonNullableInt: Int = nullableInt!! // asserting and smart cast

Safe Calls ?.

处理每个空值非常繁琐。幸运的是 Kotlin 提供了一个语法称为 “safe calls”,这个语法运行程序员只有在指定引用不为空值的时候执行动作。
下面定义两个数据类,演示一下这个特性:

data class Person(val country: Country?)

data class Country(val code: String?)

我们可以看到 countrycode 字段都是可以为空的类型
访问这些字段的时候,我们可以使用 safe call 语法:

val p: Person? = Person(Country("ENG"))

val res = p?.country?.code

assertEquals(res, "ENG")

当 p 为空值的时候,safe call 语法返回 null

val p: Person? = Person(Country(null))

val res = p?.country?.code

assertNull(res)

let()方法

只想在一个引用不是空值的时候执行动作,我们可以使用 let 操作符。
这里有一个值得列表,里面有空值

val firstName = "Tom"
val secondName = "Michael"
val names: List<String?> = listOf(firstName, null, secondName)

然后,我们可以使用 let 函数在列表里面的每个非空值上执行操作

var res = listOf<String?>()
for (item in names) {
item?.let { res = res.plus(it) }
}

assertEquals(2, res.size)
assertTrue { res.contains(firstName) }
assertTrue { res.contains(secondName) }

also() 方法

如果我们想要做一些额外的操作,例如:在操作每个非空值的时候记录日志,我们可以使用 also() 方法,连接在 let()后面

var res = listOf<String?>()
for (item in names) {
item?.let { res = res.plus(it); it }
?.also{it -> println("non nullable value: $it")}
}

这样就可以打印出每个非空值的信息

non nullable value: Tom
non nullable value: Michael

run()方法

Kotlin 有一个 run() 方法用来在空值引用上执行一些操作。他和 let() 很像,但是是在函数体内,run() 方法操作这个引用,代替函数参数。

var res = listOf<String?>()
for (item in names) {
item?.run{res = res.plus(this)}
}

Nullable Unsafe Get

Kotlin也提供了一个不安全的操作符,可以跳过空值检查逻辑来获取可以为空值的字段,但是要小心使用。
两个 “!!” 可以从一个空值引用中获取数值并且在为空时抛出 NullPointerException 异常。这个和 Optional.get() 操作是等价的。

var b: String? = "value"
b = null

assertFailsWith<NullPointerException> {
b!!.length
}

如果这个空值引用的值非空,那么操作可以成功执行。

val b: String? = "value"

assertEquals(b!!.length, 5)

从列表中过滤空值

Kotlin 的 List 类有一个工具方法 filterNotNull() 只返回列表中的非空数值。

val list: List<String?> = listOf("a", null, "b")

val res = list.filterNotNull()

assertEquals(res.size, 2)
assertTrue { res.contains("a") }
assertTrue { res.contains("b") }

这是一个非常有用的想法,封装了常用的操作逻辑,不用我们自己来实现。

结论

在这里,我们深入探讨了 Kotlin 的空值安全特性。我们看到了两种类型,空值和非空值。我们使用 safe callelvis 操作符实现了优雅的处理空值逻辑

样例和代码在GitHub,是一个Maven项目