Kotlin Learning之空指针安全


在Kotlin中,如果一个变量可能为null,那么要在变量类型后面加上 ?,例如:

var s:String?=null

如果不加?,表示该变量一定不为null,这样在编译时就可以确定变量引用是否会产生潜在的空指针。

下面来看几个问题:

可空类型的调用

调用方式

如果一个类型是可空类型,那么有两种方式可以调用:

?.调用方式

例如:

    var s: String? = null
    println(s?.length)

如果s 不为null,那么s?.length返回的是s.length,如果s为null,那么s?.length返回的是null。

那么String?对应的java类型是什么呢?是String吗?
我们来看下下面的kotlin代码对应的java code:


fun f1(){
    var s: String? = null
    println(s?.length)
}

fun f2(){
    var ss: String? = "ss"
    println(ss?.length)
}

声明两个变量sss,初始化值分别为null和非null,我们看下对应的java code:

   public static final void f1() {
      String s = (String)null;
      Object var1 = null;
      boolean var2 = false;
      System.out.println(var1);
   }

   public static final void f2() {
      String ss = "ss";
      Integer var1 = ss.length();
      boolean var2 = false;
      System.out.println(var1);
   }

可以看到如果在编译时期可以确定变量为null,那么会使用一个值为null的Object变量,将原变量调用的表达式直接替换为Object变量。
如果在编译时期可以确定变量不为null,那么会将原变量中的声明去掉,产生一个对应的java类型变量,将原变量调用的表达式替换为java类型变量表达式。

为什么一定要重点强调在编译时期可以确定变量值是null还是非null呢?我们来看下在编译时无法确定变量值的情况:

fun f3(s:String?){
    println(s?.length)
}

由于s是通过方法传递参数获取的,所以在编译时期无法确定s是null还是非null。我们来看下这个情况下,f3对应的java code:


   public static final void f3(@Nullable String s) {
      Integer var1 = s != null ? s.length() : null;
      boolean var2 = false;
      System.out.println(var1);
   }

就是对s加上了@Nullable注解,并且在具体调用的时候对s是否为null进行了判断。

总结一下:

如果在编译时期可以确定可空变量的值,那么会直接使用该变量的值。如果无法确定,那么会使用对应的java类型的变量,但是在使用时会判断java变量是否为null。

!!.调用方式

如果一个类型是可空类类型,也可以使用!!来调用,与?.不同的是,如果变量为null,则会直接抛出空指针异常。

fun f1(){
    var s: String? = null
    println(s!!.length)
}

Exception in thread “main” kotlin.KotlinNullPointerException

!!.?.实现原理相似,只不过,如果?.对应的变量为null,会直接返回null。但是如果!!.对应的变量为null,会直接抛出异常。

Java与Kotlin交互

Kotlin中的空指针安全也不是绝对的。
想象一个场景,如果Java调用Kotlin中的方法,Kotlin方法的参数是不可为null的,那么Java调用时可以传递null吗?
答案是可以的。我们来看个例子:

// Hello.kt
fun f(s:String){
    println(s.length)
}

kotlin中定义了一个方法f,需要一个非空的String类型参数。如果在Kotlin中调用这个方法又传递了空参数,那么无法编译通过。如果在java中调用了该方法:


public class HelloJava {

    public static void main(String[] args) {
        HelloKt.f(null);
    }

}

编译器是无法检测出来的异常的,在运行时会报异常。

Exception in thread “main” java.lang.IllegalArgumentException: Parameter specified as non-null is null: method HelloKt.f, parameter s
at HelloKt.f(Hello.kt)
at HelloJava.main(HelloJava.java:4)

对于public的kotlin函数,编译器会对每个非空参数进行检查。这种检查在函数调用的时候就会执行,而不是等到参数使用的时候。

子类型

需要注意的是StringString?是两个完全不同的类型,没有父类和子类的关系,但是StringString?子类型。这也是子类与子类型的微小差异之处。
子类型的定义是任何时候如果需要A类型的值,都可以使用B类型的值来代替,那么B类型就是A类型的子类型。
比如说:

    var s = "s"
    var ss: String? = s

ss变量需要将一个String?类型复制给它,这里可以使用String类型来代替。所以StringString?的子类型。

反过来的话则无法编译通过:

// 无法编译通过
    var ss: String? = "dd"
    var s: String = ss
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值