我们知道Java在开发及使用的过程中,存在空指针问题。一般出现在忘记给变量赋值或者因为某些原因调用了可能返回空置的方法,在调用的时候都会抛出空指针异常。

在Java 8之前需要我们编写判断空的逻辑代码。

if (x != null) {
    x.method(...);  
}

在Java 8的时候,提供了Optional类来缓解此问题,但是也不能完全解决。

在谈Kotlin的优势的时候,大家都会想到空指针安全这一点,那么Kotlin又是如何避免这些问题的呢?Kotlin规定,在定义一个变量的时候,必须指定这个变量是否可以为空。这样开发者能在编译之前发现问题,并将错误扼杀在摇篮。

一、可空类型

默认声明的变量是不能为 null 的,如果需要使变量能为 null, 需要在类型后添加 ?。例如:

val name: String = null // 编译错误
var name = "harry"
name = null // 编译错误
// 正确的写法
var name: String? = null
var name: String? = "harry"
name = null

同样,方法的返回值,如果可能返回空,也要加 ?。

fun test(): String? = if ... else null

二、安全操作符

安全操作符的语法为:在对象后面添加 ? 作为后缀。安全操作符即可以用于访问属性,也可以用于调用方法。

Kotlin 规定在调用可空类型对象的方法的时候,必须使用安全操作符,否则编译报错。

安全操作符相当于在访问对象时,先进行一次非空检查。若为空值,则什么都不做,如果不为空值则继续执行操作符后面的逻辑。

通过安全操作符,我们就可以安全访问可空变量。例如:

var name: String? = "harry"
if (name != null) {
  println(name.length)
}

例如下面的逻辑,进行了多次空检查,但代码只需要一行,是不是很简洁明了:

fun getCountryName(persion: Persion?) = persion?.city?.country?.name

三、非空断言

当我们知道变量是不可能为 null 的, 可以用非空断言 !!,这样编译器就不会检查空指针,例如,下面我们可以通过非空断言 !! 将可空对象赋值给非空变量:

val nullableString: String? = "hello world"
val string: String = nullableString!!

var name: String? = null
name = "jason"
val len = name!!.length

!! 非空断言只是告诉编译器可以在可空对象上进行访问操作,它本身不会处理空值。所以,这种操作是不安全的,在使用的时候一定要注意。

五、Elvis操作

Java里面, 有个三元操作?:, 能够实现 if … else … 的赋值。

Kotlin 也有这个操作符, 但是用法和效果不一样,例如:

val nullableName: String? = ...
val name: String = nullableName ?: "default_name"

如上代码意思了, 如果 nullableName 为 null, 就赋值 “default_name”。?:主要用于如果变量为 null就设置默认值。

六、安全类型转换

如果想安全地进行类型转换, 当转换失败时结果 null, 这种情况可用 as?。

val location: Any = "London"
val safeString: String? = location as? String
val safeInt: Int? = location as? Int

七、判空原理

为了更好的理解Kotlin空指针的原理。下面来看一下Kotlin是如何利用工具给开发者在编译前给出提示的。
以下面的Java判断空值方法为例:

public void foo(Bar bar) {}

对于这样一个典型的方法,如果传入的参数为null,那么通常的处理方式是检查输入:

public void foo(Bar bar) { 
  if (bar == null) 
    throw IllegalArgumentException();
}

如果调用该方法时传入了null,那么它会抛出异常,并提供有用的信息。但这要到运行的时候才能看到。如果方法定义本身就能明确表达不接受null参数的意图就好了。于是,上述代码可以进一步改进为:

public void foo(@NotNull Bar bar) { 
if (bar == null) 
    throw IllegalArgumentException()
}

如此一来,像IntelliJ IDEA这样的工具在检测到调用者可能传入null时就会提醒开发者。这样的代码没错,但就是有点啰嗦。

Kotlin采用了一种不同的null处理方式。它对可空类型和不可空类型作了区分,可空的类型后面会跟一个问号,比如Bar?,而Bar类型的变量则不可为空。于是,在Kotlin中,上述Java代码就变成了下面这样:

public fun foo(bar : Bar) {}

Kotlin非常简洁且富有表现力。这从上面的例子可见一斑。

八、总结

Kotlin引入了空安全的概念,并在编译时开展对象是否为空的校验。相关的操作符说明概括如下:

1、声明对象实例时,在类型名称后面加问号,表示该对象可以为空;

2、调用对象方法时,在实例名称后面加问号,表示一旦实例为空就返回null;

3、新引入运算符“?:”,一旦实例为空就返回该运算符右边的表达式;

4、新引入运算符“!!”,通知编译器不做非空校验,运行时一旦发现实例为空就扔出异常;