Kotlin的可空性

参考链接

《Kotlin实战》

可空性是Kotlin的一个重要特性,可以帮助我们避免发生NullPointerException.

可空类型

1
2
3
int strLen(String s){
return s.length();
}

上面的Java代码如果我们传入空参数就会导致NullPointerException.

但是,和上面等价含义的Kotlin语句在编译期就能标记出错误,如下:

1
2
3
4
fun strLen(s: String) = s.length

>>> strLen(null)
ERROR: Null can not be a value of non-null type String

函数参数被声明为String类型,所以必须包含一个非null的String实例,这样在Kotlin中就保证了不会在运行时抛出NullPointerException.

和Java不同的是Kotlin中声明的变量默认都是非null的,如果可空则需要追加?号来声明。

Type? = Type or null

1
fun strLen(s: String?) = s.length

上面这行代码编译器会直接标记错误,因为s.length的s可能会是null

另外不能将可空类型的值赋值给非空类型的变量。

1
2
3
4
var x: String? = null
var y: String = x

ERROR: Type mismatch: inferred type is String? but String was expected

安全调用运算符:”?.”

等价于: if not null

1
2
3
4
5
if (s != null) s.toUpperCase() else null
|
//等价于
|
s?.toUpperCase()

通过该运算符可以很方便的进行非null判断了,这个在实际开发中很实用。

Elvis运算符:”?:”

等价于:if not null and else

1
2
3
fun foo(s: String){
val t: String = s ?: ""
}

如果s为null,结果会是一个””字符串,使用该运算符可以将null替换为一个默认值。

安全转换:”as?”

as?运算符尝试把值转换成指定的类型,如果值不是合适的类型就返回null

1
2
3
4
5
6
7
8
class Person(val firstName: String, val lastName: String){

override fun equals(o: Any?): Boolean{
val otherPerson = o as? Person ?: return false
return otherPerson.fristName == firstName &&
otherPerson.lastName = lastName
}
}

非空断言:”!!”

1
2
3
4
5
6
7
8
fun ignoreNulls(s: String?){
val sNotNull: String = s!!
println(sNotNull.length)
}

>>> ignoreNulls(null)

Exception in thread "main" kotlin.KotlinNullPointerException

!!运算符可以把任何类型转换成非空类型,如果对null值做非空断言则会抛出异常。

“let”函数

1
2
3
4
5
fun sendEmailTo(email: String){
/* send Email */
}

if(email != null) sendEmailTo(email)

上面我们显式的判断了email不为null,可以使用let函数达到同样的目的。

1
email?.let {email -> sendEmailTo(email)}

let函数可以让我们隐式的判断非空参数并传递给调用函数。

延迟初始化属性

Kotlin中要求在构造方法中初始化所有属性,如果某个属性是非空类型,你就必须提供非空的初始化值,否则,你就必须使用可空类型。

但是有时候我们在构造中无法进行初始化,但是又需要在另一个入口处初始化并且让其是非空类型(非空类型可以不用重复判断是否为空)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyService{
fun performAction(): String = "foo"
}

class MyTest{

private var myService: MyService? = null

@Before fun setUp(){
myService = MyService()
}

@Test fun testAction(){
Assert.aseertEquals("foo", myService!!.performAction())
}
}

为了解决上面的问题,可以将myService属性声明为延迟初始化属性,使用lateinit修饰符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyService{
fun performAction(): String = "foo"
}

class MyTest{

private lateinit var myService: MyService

@Before fun setUp(){
myService = MyService()
}

@Test fun testAction(){
Assert.assertEquals("foo", myService.performAction())
}
}

可空类型的扩展

1
2
3
4
var str: String? = null
println(str.isNullOrBlank())

>>> true

你是不是感到奇怪,为什么可空类型直接可以调用方法呢?

事实上isNullOrBlack()是String类的一个扩展函数,扩展函数可以允许接收者为null调用,并在函数中处理null,只有扩展函数能这样做。

和isNullOrBlack()类似的isEmptyOrNull也是String的扩展函数。

1
fun String?.isNullOrBlack(): Boolean = this == null || this.isBlank()

当你为一个可空类型(以?结尾)定义扩展函数时,意味着你可以用可空的值调用这个函数,函数体中的this可能为null。

类型参数的可空性

1
2
3
fun <T: Any> printHashCode(t: T){
println(t?.hashCode())
}

在Kotlin中所有泛型类和泛型函数的类型参数默认是可空的。上面的类型T默认是可空的。

可空性和Java

Java中的@Nullable String 等价于 Kotlin中的 String?
Java中的@NotNull String 等价于 Kotlin中的 String