Kotlin学习路(二):空指针

<本文学习郭神《第三行代码》总结>

1、可空类型

Kotlin在编译时,会有自己的判空机制,可以几乎杜绝空指针异常
首先,写一段代码:

fun setDate(a: Test) {
    a.getText()
    a.getContent()
}

这段代码没有空指针风险,并且这段代码无法传入空参数。
因为Kotlin默认所有参数不可为空,所以这里传入的参数也是不可空的。如果这里传入一个null参数,则会提示错误:Null can not bea value of a non-null type Study,也就是说,Kotlin在将空指针异常的检查提前到了编译期。
如果开发过程中,业务逻辑需要使用null参数,则需要进行可空类型操作。
可空类型很简单,就是在类名后加一各问好,比如:

Int 表示不可空整型
Int? 表示可空整型
String 表示不可控字符串
String? 表示可空字符串

那么,将上述代码修改为

fun setDate(a: Test?) {
    a.getText()
    a.getContent()
}

就可以传入null参数,就不会再提示错误。
但是,传入了空参数,会导致方法里面的调用方法getText()、getContent()出现空指针异常,无法通过Kotlin编译,所以必须要进行判空处理。

fun setDate(a: Test?) {
	if (a != null) {
	    a.getText()
	    a.getContent()
	}
}

2、判空辅助工具

(1)?. 操作符
如果需要判空处理过多,则会写很多if,导致很繁琐,而且if也无法处理全局变量,所以这里需要使用辅助工具。
比如,对以下代码进行判空处理进行简化

if (a != null) {
    a.getText()
}

简化成

a?.getText()

这里?.就是判空操作符,所以上面的代码就可以写成

fun setDate(a: Test?) {
    	   a?.getText()
    a?.getContent()
}

这样,通过使用?.操作符就可替换if

(2)?: 操作符
这个操作符左右两边都接收一个表达式,如果左边表达式结果不为空,则返回左边,否则,返回右边。

val c = if (a != null){
    a
} else{
    b
}

可以简化成

val c = a ?: b

(3)两种操作符共同使用
比如一段代码

fun getData(content : String?) : Int{
    if (content != null)
        return content.length
    return 0
}

可以简化成

fun getData(content : String?) = content?.length ?: 0

(4)!! 操作符
Kotlin判空机制也存在漏洞,因为有时候从逻辑上已经判空处理,但是编译器并不知道,还是会编译失败。

var content: String? = "aa"
fun main(){
    if (content != null){
        val text = content.toUpperCase()
    }
}

定义一个全局变量content,在main方法中进行判空处理,当不为空时执行下一步操作。
但是这段代码无法编译运行,因为toUpperCase()并不知道外部已经对content进行了判空处理,所以在调用时,还是认为存在空指针风险。
在这种情况下,要想通过编译,只能使用 !! ,进行强制通过。

var content: String? = "aa"
fun main(){
    if (content != null){
        val text = content!!.toUpperCase()
    }
}

这是一种风险写法,因为这是在强制进行通过编译,如果在开发中,出现null情况,则程序会崩溃。

(5)let
let不是操作符,也不是关键字,而是函数。这个函数提供了函数式的API编程接口,并将调用对象作为参数传递到Lambda表达式中。

obj.let { obj2 ->
//函数体
}

这里,调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中,这里,为了防止重名,将obj参数名改成了obj2,实际是同一个对象。

fun setDate(a: Test?) {
    	   a?.getText()
    a?.getContent()
}

这段代码通过?. 操作符可以正常编译,如果将这段换成if判断,则是:

fun setDate(a: Test?) {
	if (a != null){
	    	   		a?.getText()
	}
	if (a != null){
	    	   	a?.getContent()
	}
}

也就是说,使用?. 每次调用方法,都会进行一次if 判断,这样会增加逻辑运算,不简洁。所以还可以简化成

fun setDate(a: Test?) {
    a?.let { a1 -> 
        a1.getText()
        a1.getContent()
}

?. 操作符表示对象为空,什么都不做,对象不为空就调用let函数,而let函数会将a 对象本身作为参数传递到Lambda表达式中,此时a 对象肯定不为空。
当Lambda参数只有一个时,可以直接用it关键字代替,所以这里可以简化成:

fun setDate(a: Test?) {
    a?.let { 
        it.getText()
        it.getContent()
}

此外,let函数还可以进行全局变量的判空问题处理,而使用if则不行,使用if对全局变量判空则需要使用!!进行强制编译通过,使用let不会出现这种情况。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章