阅读 147

kotlin总结系列(3)——可空类型安全

kotlin总结系列(1)--基础要素

Kotlin 总结系列(2)——函数和类

一 可空性

kotlin对可空类型的显式支持,是kotlin类型系统中帮助你避免NullPointerException错误的特性

如果一个变量可以为null,对其方法的调用便是不安全的,会导致NullPointerException 。先看java中的函数:

/* java */
int strLen(String s ){
    return s.length();
}
复制代码

这个函数并不是安全的,当传的实参是null,便会抛出NullPointerException(当然可以先提前检查)

现假设你不希望传入实参为null,则如下使用非空类型,即不允许为null:

fun strLen(s:String) = s.length
复制代码

当传入null实参时,编译器会报错:

>>> strLen(null)
ERROR:Null can not be a value of a non-null type string
复制代码

上述函数,参数被声明为String类型,即非空的,所以不能传递一个null值。若想使该参数可空,即可传入null值,需显式在类型名称后面加上问号来标记:

 fun strLenSafe(s:String?) = if(s == null) 0 else s.length
复制代码

可以在任意类型后面添加问号来表示可以存储null引用,如String?,Int?,MyCustomType? 即,Type? = Type or null

注: 除非显式的把它标记为可空,否则所以常见类型默认时可空的

对于可空类型,不能直接调用它的方法或属性,也不能把它赋值给非空类型的变量,这都是不安全的:

>>> fun strLenSafe(s:String?) = s.length()
ERROR: only safe(?.) or non-null asserted(!!.) calls are allowed ...

>>> val x:String? = null
>>> val y:String = x
ERROR: Type mismatch...
复制代码

可空值最重要操作就是与null做比较,而一旦检查后,编译器便会记住并提供只能转换,如:

fun strLenSafe(s:String?) = if(s == null) 0 else s.length // 智能转换为非空
复制代码

一味用if检查,过于冗余,kotlin提供了一些运算符工具,能帮助我们用简洁的方式来处理可空值。

二 运算符

1 安全调用运算符: "?."

安全调用运算符"?.",将一次null检查和一个方法调用(或属性访问)合并成一个操作。表达式s?.toUpperCase()等同于这种繁琐写法if (s!=null)s.toUpperCase()else null

即如果调用一个非空值的方法,这次方法调用会被正常执行,但如果是null,则调用不会发生,整个表达式值为null。

注: 这种表达式的结果类型也是可空类型

fun printlnAllCaps(s:String?){
    val allCaps:String? = s?.toUpperCase()
    println(allCaps)
}

>>>printlnAllCaps("abc")
ABC
>>>printlnAllCaps(null)
null
复制代码

如果对象调用链中,有多个可空类型的属性,通常可以在同一个表达式中方便地链接多个安全调用,如下访问公司地址:

class Address(val streetAdress:String,val zipCode:Int,val city:String,val country:String)

class Company(val name :String,val address:Address)

class Person(val name:String,val company:Company?)

fun Person.countryName():String {
    val country:String? = this.company?.address?.country
    return if(country == null) "Unknown" else country //此处可优化
}

>>> val person = Person("John",null)
>>> println(person.countryName())
Unknown
复制代码

上面例子if(country == null) "Unknown" else country,用一个值和null比较,如果不为空就返回这个值,否则返回其他值。kotlin能对这种情况进行优化去掉重复代码:

2 Elvis运算符(null合并运算符): "?:"

fun foo(s:String?){
    val t:String = s ?: ""  //如果s为空,则结果是空字符串,否则则是s本身
}
复制代码

Elvis运算符接收两个运算数,如果第一个运算数不为null,运算结果就是第一个运算数;否则结果就是第二个运算数,即 foo ?: bar 的繁琐表示为:

if(foo ==null) bar else foo
复制代码

可以简化上面打印countryName的例子:

fun Person.countryName() = 
    company?.address?.country ?: "Unknown"
复制代码

了解了"if非空"检查后,接下来介绍kotlin常用的instanceof检查的安全版本:常常与安全调用及Elvis运算符一起出现的安全转换运算符

3 安全转换: "as?"

类型转换的常规kotlin运算符:as运算符,与java类型转换一样,若被转换的值不是你试图转换的类型,就会抛出ClassCastException异常。当然可以结合is来检查,但kotlin有更安全优雅的调用方式:as?

as? 运算符尝试把值转换成指定类型,如果不是合适的类型,则最终值为null 。 即foo as? Type的繁琐表达为:

if(foo is Type) foo as Type else null
复制代码

一种常用方式是把安全转换和Elvis运算符结合使用。 如equals方法的实现非常方便

class Person(val firstName:String,val lastName:String){
    override fun equals(o:Any?):Boolean{
        val otherPerson = o as? Person ?: return false
        
        return otherPerson.firstName == firstName && 
            otherPerson.lastName == lastName
    }
    
    override fun hashCode():Int = 
        firstName.hashCode()*37 + lastName.hashCode()
}

>>> p1 = Person("John","Cap")
>>> p2 = Person("John","Cap")
>>> println(p1 == p2)
true
>>> println(p1.equals(42))
false
复制代码

安全调用、安全转换和Elvis运算符都非常高效有用。但有时不需要处理null值,只需要直接告诉编译器这并不是个null值:

4 非空断言: "!!"

非空断言是kotlin提供的,最简单粗暴直率的处理。可以把任何类型转换为非空类型。 如果对null值做了非空断言,则会抛出空异常。即"foo!!"的繁琐表达为:

foo as?Foo ?: throw NullPointerException(...)
复制代码

可以使用这种断言来把可空参数转换为非空:

fun ignoreNull(s:String?){
    val sNotNull :String = s!! //转换为非空
    println(sNotNull)
}
复制代码

本质上是在表明:“我明确知道这个值不为null,如果我错了我准备好接收这个异常

5 "let"函数

let函数让处理可空表达式变得容易。let函数所做的所有事情,就是把一个调用它的对象变成lambda表达式的参数。

val s :String? ...
s.let{
    //此时lambda参数为 String?
}

s?.let{
    //此时lambda参数为 String
}
复制代码

如果和安全运算符使用,就能有效地把调用let函数的可空对象转为非可空对象。 即 foo?.let{...}的繁琐表达是当foo不为null时,调用lambda中的代码,当foo为null时,则什么也不发生

如传递一个可空参数到一个接收非空参数的函数:

fun sendEmailTo(email:String){...}

>>>val email:String? = ...
>>> sendEmailTo(email)
ERROR:Type mismatch...
复制代码

但可以这么优雅安全地调用:

email?.let{sendEmailTo(it)}
复制代码
关注下面的标签,发现更多相似文章
评论