在许多的编程语言(例如Java)中最常见的遇到的陷阱之一,就是访问引用的成员会导致空指针异常的情况(NullPointerException),在Kotlin中的做了空安全的设计,从而避免了NullPointerException。
Kotlin的空安全设计
简单来说就是通过IDE的提示来避免调用null的对象,从而避免了NullPointerException,在androidx里就有通过一个@NonNull的注解就可以标记变量是否为空,然后IDE会帮助检查。到了Kotlin这里就有了语言级别的支持,IDE的提示级别从警告变成了错误,拒绝编译。接下来看看Kotlin的实现空安全做了哪些措施。
变量默认都是不为空的

可以看到在上面的定义的时候赋值为空的时候,编译器就会提示出现错误,所有的默认变量都是不允许为空的。那么有时间在请求服务器数据的时候,有一些变量是会出现为空的情况,那该怎么办呢?
可以在变量的类型上面加上一个?号,这样就可以解除非空的限制。

那么这样不是还是会出现空指针异常的情况吗。
安全的调用(?.)
在上面提到通过?号可以解除非空的限制,既然赋值可能会出现为空,那么为了避免空指针的异常,kotlin的做法就是在调用方进行处理——安全的调用。通过?.的方式进行调用。

上面的?.的意思就是,如果str非空,就返回b.length,否则返回null。如果最后返回为null的话,我们还可以进行一步的处理。
使用Elvis操作符?:
在上面提到的如何str为空,那么length返回为空,这样好像也不太好,这是我们可以使用Elvis操作符:

意思就是当前面为空的时候就返回-1。Elvis还有另一种常见的用法:


!!操作符
!!是非空断言运算符,也是避免空指针异常的手段方式之一,若该值为空则抛出异常。

意思就编译器不再帮助检查,有什么后果需要程序员去承担,这种方式,实际上和Java就没上两样了。
安全的类型转换
如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException。 另一个选择是使用安全的类型转换,如果尝试转换不成功则返回 null:
val aInt: Int? = a as? Int
可控类型的集合
如果你有一个可空类型元素的集合,并且想要过滤非空元素,你可以使用 filterNotNull 来实现:
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
Kotlin的空安全设计,带来了很多好处,但是有的时候我们不一定能在定义的时候赋值,例如在Android开发的时候初始化一个控件的时候,findViewById()是需要在onCreate之后就调用的,那么这种情况怎么处理比较合适呢?
一:利用lateinit关键字进行延迟初始化
延迟初始化的意思是,告诉编译我无法第一时间去初始化,但是在使用前肯定会完成初始化。
private lateinit var textView:TextView
override fun onCreate(savedInstanceState: Bundle?) {
textView=findViewById(R.id.textView)
}
但是延迟初始化并不好,这样跟Java的属性没什么区别了,体验不到了空安全设计到带来的好处,而且 lateinit只能修饰var的属性,那么val的呢?
二:利用by Lazy惰性初始化
lazy()
是接受一个 lambda 并返回一个Lazy <T>
实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用get()
会执行已传递给lazy()
的 lambda 表达式并记录结果, 后续调用get()
只是返回记录的结果。也就是说只有第一次调用该属性的时候,委托方法才会被执行。并且只能修饰val的属性。
为了进一步安全使用,上面的TextView的初始化,就可以使用by lazy()了
private val textView by bindView<TextView>(R.id.textView)
private val Activity.viewFind:Activity.(Int)->View?
get()={
findViewById(it)
}
fun <V:View> Activity.bindView(id:Int):Lazy<V> = lazy{
viewFind(id) as V
}
网友评论