这篇文章我们一起来学习Kotlin空安全的设计
在Kotlin中可以通过 IDE 的提示来避免调用 null 对象,从而避免 NullPointerException。其实在 androidx 里就有支持的,用一个注解就可以标记变量是否可能为空,然后 IDE 会帮助检测和提示,我们来看下面这段 Java 代码:
//Java
@NonNull
View view = null;
// IDE 会提示警告,'null' is assigned to a variable that is annotated with @NotNull
而到了 Kotlin 这里,就有了语言级别的默认支持,而且提示的级别从 warning 变成了 error(拒绝编译):
//Kotlin
var view: View = null
// IDE 会提示错误,Null can not be a value of a non-null type View
在 Kotlin 里面,所有的变量默认都是不允许为空的,如果你给它赋值 null,就会报错,像上面那样。
这种有点强硬的要求,其实是很合理的:既然你声明了一个变量,就是要使用它对吧?那你把它赋值为 null 干嘛?要尽量让它有可用的值啊。Java 在这方面很宽松,我们成了习惯,但 Kotlin 更强的限制其实在你熟悉了之后,是会减少很多运行时的问题的。
不过,还是有些场景,变量的值真的无法保证空与否,比如你要从服务器取一个 JSON 数据,并把它解析成一个 Person 对象:
//Kotlin
class Person {
var name: String = null // 这样写会报错,但该变量无法保证空与否
}
这个时候,空值就是有意义的。对于这些可以为空值的变量,你可以在类型右边加一个 ?
号,解除它的非空限制:
//Kotlin
class Person {
var name: String? = null
}
加了问号之后,一个 Kotlin 变量就像 Java 变量一样没有非空的限制,自由自在了。
你除了在初始化的时候可以给它赋值为空值,在代码里的任何地方也都可以:
//Kotlin
var name: String? = "huahuadashen"
...
name = null // 原来不是空值,赋值为空值
这种类型之后加 ?
的写法,在 Kotlin 里叫可空类型。
不过,当我们使用了可空类型的变量后,会有新的问题:
由于对空引用的调用会导致空指针异常,所以 Kotlin 在可空变量直接调用的时候 IDE 会报错:
//Kotlin
var view: View? = null
view.setBackgroundColor(Color.RED)
// 这样写会报错,Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type View?
「可能为空」的变量,Kotlin 不允许用。那怎么办?我们尝试用之前检查一下,但似乎 IDE 不接受这种做法:
//Kotlin
if (view != null) {
view.setBackgroundColor(Color.RED)
// 这样写会报错,Smart cast to 'View' is impossible, because 'view' is a mutable property that could have been changed by this time
}
这个报错的意思是即使你检查了非空也不能保证下面调用的时候就是非空,因为在多线程情况下,其他线程可能把它再改成空的。
那么 Kotlin 里是这么解决这个问题的呢?它用的不是 .
而是 ?.
:
//Kotlin
view?.setBackgroundColor(Color.RED)
这个写法同样会对变量做一次非空确认之后再调用方法,这是 Kotlin 的写法,并且它可以做到线程安全,因此这种写法叫做「safe call」。
另外还有一种双感叹号的用法:
//Kotlin
view!!.setBackgroundColor(Color.RED)
意思是告诉编译器,我保证这里的 view 一定是非空的,编译器你不要帮我做检查了,有什么后果我自己承担。这种「肯定不会为空」的断言式的调用叫做 「non-null asserted call」。一旦用了非空断言,实际上和 Java 就没什么两样了,但也就享受不到 Kotlin 的空安全设计带来的好处(在编译时做检查,而不是运行时抛异常)了。
以上就是 Kotlin 的空安全设计。
理解了它之后再来看变量声明,跟 Java 虽然完全不一样,只是写法上不同而已。
很多人在上手的时候都被变量声明搞懵,原因就是 Kotlin 的空安全设计所导致的这些报错:
?
设置为可空的时候,使用的时候因为「可能为空」又报错。明白了空安全设计的原理后,就很容易能够解决上面的问题了。
关于空安全,最重要的是记住一点:所谓「可空不可空」,关注的全都是使用的时候,即「这个变量在使用时是否可能为空」。
今天就先学习到这里