Android Kotlin null值安全性

Kotlin 类型系统的设计目标就是希望消除代码中null引用带来的危险。

Kotlin 的类型系统致力于从我们的代码中消除NullPointerException. 只有以下情况可能导致NPE:

  • 明确调用 throw NullPointerException()
  • 使用 !! 操作符, 详情见后文
  • 外部的 Java 代码导致这个异常
  • 初始化过程中存在某些数据不一致 (在构造器中使用了未初始化的 this)

在 Kotlin 中, 类型系统明确区分可以指向null的引用 (可为null引用) 与不可以指向 null的引用 (非null引用). 比

比如, 一个通常的String类型变量不可以指向null:

var a: String = "abc"
a = null // 编译错误

要允许null值, 我们可以将变量声明为可为null的字符串, 写作String?:

var b: String? = "abc"
b = null // ok

在条件语句中进行 null 检查

首先, 你可以明确地检查b是否为null, 然后对null和非null的两种情况分别处理:

val l = if (b != null) b.length else -1

编译器将会追踪你执行过的检查, 因此允许在if内访问length属性 . 更复杂的条件也是支持的:

if (b != null && b.length > 0) {
    print("String of length ${b.length}")
} else {
    print("Empty string")
}

注意, 以上方案需要的前提是,b的内容不可变(也就是说, 对于局部变量的情况, 在null值检查与变量使用之间, 要求这个局部变量没有被修改, 对于类属性的情况, 要求是一个使用后端域变量的val属性, 并且不允许被后代类覆盖), 因为, 假如没有这样的限制的话,b就有可能会在检查之后被修改为null值.

安全调用

第二个选择方案是使用安全调用操作符, 写作?.:

b?.length

如果b不是null, 这个表达式将会返回b.length, 否则返回null. 这个表达式本身的类型为Int?.

安全调用在链式调用的情况下非常有用. 比如, 假如雇员Bob, 可能被派属某个部门Department(也可能不属于任何部门), 这个部门可能存在另一个雇员担任部门主管, 那么, 为了取得Bob所属部门的主管的名字, (如果存在的话), 我们可以编写下面的代码:

bob?.department?.head?.name

这样的链式调用, 只要属性链中任何一个属性为null, 整个表达式就会返回null.

如果需要只对非null的值执行某个操作, 你可以组合使用安全调用操作符和let:

val listWithNulls: List = listOf("A", null)
for (item in listWithNulls) {
    item?.let { println(it) } // 打印 A, 并忽略 null 值
}

Elvis 操作符

假设我们有一个可为null的引用r, 我们可以用说, “如果r不为null, 那么就使用它, 否则, 就使用某个非null的值x”:

val l: Int = if (b != null) b.length else -1

除了上例这种完整的if表达式之外, 还可以使用 Elvis 操作符来表达, 写作?::

val l = b?.length ?: -1

如果?:左侧的表达式值不是null, Elvis 操作符就会返回它的值, 否则, 返回右侧表达式的值. 注意, 只有在左侧表达式值为null时, 才会计算右侧表达式.

注意, 由于在 Kotlin 中throwreturn都是表达式, 因此它们也可以用在 Elvis 操作符的右侧. 这种用法可以带来很大的方便, 比如, 可以用来检查函数参数值是否合法:

fun foo(node: Node): String? {
    val parent = node.getParent() ?: return null
    val name = node.getName() ?: throw IllegalArgumentException("name expected")
    // ...
}

!! 操作符

对于NPE的热爱者们来说, 还有第三个选择方案. 我们可以写b!!, 对于b不为null的情况, 这个表达式将会返回这个非null的值(比如, 在我们的例子中就是一个String类型值), 如果bnull, 这个表达式就会抛出一个NPE:

val l = b!!.length

所以, 如果你确实想要NPE, 你可以抛出它, 但你必须明确地提出这个要求, 否则NPE不会在你没有注意的地方无声无息地出现.

安全的类型转换

如果对象不是我们期望的目标类型, 那么通常的类型转换就会导致ClassCastException. 另一种选择是使用安全的类型转换, 如果转换不成功, 它将会返回null:

val aInt: Int? = a as? Int

#可为 null 的类型构成的集合
如果你的有一个集合, 其中的元素是可为null的类型, 并且希望将其中非null值的元素过滤出来, 那么可以使用filterNotNull函数.

val nullableList: List = listOf(1, 2, null, 4)
val intList: List = nullableList.filterNotNull()

你可能感兴趣的:(Android,Kotlin)