Kotlin刨根问底(一):你真的了解Kotlin中的空安全吗?

这篇以前写过,删了一些废话,修正了一些错误,弄了下排版,最近开始连载这个系列,
行文结构:要点提炼(方便回顾) + 常规操做 + 源码层面摸索实现原理
内容部分摘取自:《Kotlin实用指南》,感兴趣的能够订阅一波 ~web


0x一、要点提炼


  • Kotlin中经过「非空类型」与「可空类型」来规避 NPE
  • 可空操做符「`?`」、安全调用操做符「`?.`」、非空断言运算符「`!!`」
  • Elvis操做符「`?:`」若是不为空返回它,不然返回另外一个值;
  • 安全的类型转换「`as?`」
  • ?.let{}
  • 不是绝对的空指针安全:Kotlin调用返回空的Java代码;
  • Kotlin空安全的实现原理:


0x二、Kotlin里的空安全用法


NullPointerException(NPE),空指针异常,发生在运行时引用一个空对象的方法时会抛出apache

Java中常见的规避手段是:编程

在对象使用前进行判空:if (obj != null)安全

多层判空时显得不是很美观,而Kotlin中是「空安全」的:app

编译期 就处理Null编程语言

空安全不是Kotlin特有的,其余不少编程语言也有,下面简述下Kotlin空安全的相关用法。函数


① 非空类型与可空类型

Kotlin中经过「非空类型」和「可空类型」来规避NPE,非空类型不能设置为Null值:post

可空类型能够设置为Null值,在类型后加上 可空操做符(?) 便可,代码示例以下: ui

可空类型,直接访问它的属性或方法,会报错:spa

能够经过 安全调用操做符(?.)非空断言运算符(!!) 来解决,代码示例以下:

运行结果以下


② Elvis操做符(?:)

三目条件运算符的简略写法:若是不是空,就返回它,不然返回另外一个值。代码示例以下:


③ 安全的类型转换

Kotlin中能够使用as关键字来进行类型转换,而使用as?表示安全类型的转换。最多见的使用场合,后台接口返回一个参数,须要咱们本身作下类型转换,直接用as的话,若是参数为null或者非String类型,就可能引起异常,若是用as? ,参数异常则不会转换,代码示例以下:

运行结果以下


④ ?.let{}

let函数除了可用于在同一个做用域下操做变量外(代码更优雅)外:

tvTitle.let {
    it.text = "标题"
    it.textSize = 18.0f
    it.gravity = Gravity.LEFT or Gravity.CENTER
    it.setOnClickListener {}
}
复制代码

还能够用做作判null操做,好比下面这样的代码:

test咱们已经设置了一个值,可是仍是报错,添加上一个非空断言便可:

还有个更优雅的写代码方式,就是用2,修改后的代码以下:

另外?.let{}遍历集合会忽略null值,只对非null值执行操做;除此以外,还能够用集合操做函数filterNotNull过滤出非空元素。


0x三、不是绝对的空指针安全


如题,Kotlin中并非绝对的空指针安全,最多见的就是在Kotlin去调Java代码,好比下面这个例子:

//Java
public class Test {
    public static String getMsg() {
        return null;
    }
}

//Kotlin
fun main() {
    println(Test.getMsg().length)
}
复制代码

运行后就直接报错

当你与Java代码进行互操做时,Null安全性确实被破坏了,固然想规避这个问题也很简单,加个?便可,示例以下:

fun main() {
    println(Test.getMsg()?.length)
}
复制代码

运行结果以下

Tips:也能够为Java代码@NotNull注解来解决~


0x四、Kotlin是如何实现空安全的


接着咱们来探究下,Kotlin中究竟是怎么实现空安全的,写下简单的代码:

fun test_1(str: String) = str.length
fun test_2(str: String?) = str?.length
fun test_3(str: String?) = str!!.length
fun test_4(str: Any?) { str as String }
fun test_5(str: Any?) { str as? String }
复制代码

接着依次点击:Tools -> Kotlin -> Show Kotlin Bytecode,生成字节码:

生成后的Java代码以下:

接着分析一波,首先是test_1函数,能够看到这里有一个@NotNull的注解,而后是:

Intrinsics.checkParameterIsNotNull(str, "str");
复制代码

Intrinsics是Kotlin的一个内部类,定位到checkParameterIsNotNull函数:

再跟:

行吧,其实就是直接对参数判空,若是为空抛出参数为空的异常。接着看下test_2函数,比较简单,判断是否为空,不为空的话,调用对应的方法,不然返回一个null。再接着是test_3函数,直接判断是否为空,空的话直接抛出Npe异常。

而后是test_4函数,判空,若是空抛出类型转换异常,不然执行后续代码;最后是test_5函数,建立一个新对象把参数的值传给他,而后进行类型判断,若是不是特定类型,对象赋值null,而后把执行类型强转后的对象赋值给一个新的对象。

综上,Kotlin中对空安全背后的处理套路以下:

  • 一、非空类型的属性编译器添加@NotNull注解,可空类型添加@Nullable注解;
  • 二、非空类型直接对参数进行判空,若是为空直接抛出异常;
  • 三、可空类型,若是是?.判空,不空才执行后续代码,不然返回null;若是是!!,空的话直接抛出NPE异常。
  • 四、as操做符会判空,空的话直接抛出异常,不为空才执行后续操做,没作类型判断!运行时可能会报错!
  • 五、as?则是新建一个变量,参数赋值给它,而后判断是否为特定类型,赋值为null,接着把这个变量的值赋值给另外一个新的变量,这里有一点注意:as?处理后的参数可能为空!!!因此调用as?转换后的对象还须要添加安全调用操做符(?.)

提醒:(这里直接用编译后Java代码的缘由是比较直观~)

  • @NotNull注解对应字节码里的:@Lorg/jetbrains/annotations/NotNull;
  • @Nullable注解对应字节码里的:@Lorg/jetbrains/annotations/Nullable;

参考文献


相关文章
相关标签/搜索