在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)
}
声明两个变量s
和ss
,初始化值分别为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函数,编译器会对每个非空参数进行检查。这种检查在函数调用的时候就会执行,而不是等到参数使用的时候。
子类型
需要注意的是String
与String?
是两个完全不同的类型,没有父类和子类的关系,但是String
是String?
的子类型。这也是子类与子类型的微小差异之处。
子类型的定义是任何时候如果需要A类型的值,都可以使用B类型的值来代替,那么B类型就是A类型的子类型。
比如说:
var s = "s"
var ss: String? = s
ss变量需要将一个String?
类型复制给它,这里可以使用String
类型来代替。所以String
是String?
的子类型。
反过来的话则无法编译通过:
// 无法编译通过
var ss: String? = "dd"
var s: String = ss