Kotlin的空安全

发布时间 2023-06-01 12:17:00作者: jqc

Kotlin中的可空类型

在Kotlin中可以通过在类型后加一个?来表示该类型是可空类型,例如:

var name : String?
var id: String

上述代码表示name是一个可空的String类型变量,而id是一个不可为空的String类型变量。
如果在代码中试图将一个可空的值赋值给id,则会导致如下的编译错误:

Type Mismatch.
Required: String
Found: String?

访问可空类型的变量

在Kotlin中可以通过?.操作符安全地访问可空类型的变量,例如:

class Test {

    fun test(name: String?) {
        println(name?.length)	//如果name是null,这里不会crash,而是输出null
    }

}

还可以使用?:运算符来额外处理当可空类型的值为空时的情况,例如:

class Test {

    fun test(name: String?) {
        println(name?.length ?: 0) //如果name是null,则输出0
    }

}

如果我们确定访问一个可空类型的变量时这个变量的值一定是非空,那么可以使用!!来将可空类型变量当做非空类型变量来访问,例如:

class Test {
    var name: String? = null

    fun test() {
        name = "william"
        println(name!!.length)	//如果这里name实际是空,则会抛出NPE
    }
}

另外,对于类型转换我们还可以使用as?运算符来安全地进行类型强转,例如:

class Test {
    fun test(name: String?) {
        var num = name as? Int  //如果使用as则会抛出ClassCastException
        var str = name as String //这里可能会抛出ClassCastException
    }
}

上述代码中name强转成Int类型会失败,但是不会抛出ClassCastException,而是返回null。试图将一个可空类型变量强转成非空类型变量是危险的,上述代码中如果name的值是null,那么强转成String类型就会引发ClassCastException

Kotlin空安全的实现原理

Kotlin主要通过编译时检查以及运行时检查的机制来实现空安全,以如下Kotlin代码为例。

class Test {

    fun test(name: String?) {
        println(name?.length ?: 0) //如果name是null,则输出0
        println(name!!.length)
        println(name as? Int)
    }

}

我们将其转成字节码后再反编译可以得到如下Java代码:

public final class Test {

   public final void test(@Nullable String name) {
      int var2 = name != null ? name.length() : 0;
      System.out.println(var2);
      Intrinsics.checkNotNull(name);
      var2 = name.length();
      System.out.println(var2);
      String var10000 = name;
      if (!(name instanceof Integer)) {
         var10000 = null;
      }

      Integer var3 = (Integer)var10000;
      System.out.println(var3);
   }

}

可以看出对于可空的参数,Kotlin在编译的时候会加上@Nullable注解。
使用?.操作符访问可空类型实际就是在访问前会先判断可空类型的变量的值是否为null
?:操作符则转换成了Java中的三元运算法,判断条件是可空类型的变量的值非空。
!!操作符则是在访问可空类型变量前调用Intrinsics.checkNotNull函数断言参数非空,如果为空则抛出NPE
as?操作符则是通过Java中的instanceof来判断类型是否符合强转的目标类型,不符合则返回null