Kotlin-?, ?.,?:,!!操作符,lateinit,::xxx.isInitialized的使用,let函数——空指针相关特性(第一行代码Kotlin学习笔记4)

1. 空指针检查

空指针是一种不受编程语言检查的运行时异常,只能由程序员主动通过逻辑判断来避免,但即使是最出色的程序员,也不可能将所有潜在的空指针异常全部考虑到。
在Java中,如果我们想要避免空指针引发的问题,我们可以这么写:

public void doStudy(Study study) {
    if (study != null) {
        study.readBooks();
        study.doHomework();
    }
}

但你总有忘记判断的时候,然而在Kotlin中,你可以完美的避免这个问题,Kotlin中引入了一个可空类型系统的概念,它利用编译时判空检查的机制几乎杜绝了空指针异常。
上述代码如果写成Kotlin版本,如果我们在调用时传入null,那么编译器回直接报错:在这里插入图片描述
那如果我们的业务逻辑需要某个参数或变量为空,怎么办呢?Kotlin提供了另外一套可为空的类型系统,就是在类名的后面加上一个问号。比如,Int表示不可为空的整型,而Int?就表示可为空的整型;String表示不可为空的字符串,而String?就表示可为空的字符串。但当我们使用可为空的类型系统时,需要在编译时期就把所有的空指针异常都处理掉才行。否则编译器仍然会有错误提示。
在这里插入图片描述
所以,我们必须这么写,才可以编译通过:

fun doStudy(study: Study?){
    if(study != null){
    	study.readBooks()
    	study.doHomework()
    }
}

2. 操作符

?. 操作符

但是如果我们处处这么写,代码会很啰嗦,而且也无法解决全局变量的盘空问题,Kotlin仍然有解决方案,Kotlin提供了一系列的辅助工具,使开发者能够更轻松地进行判空处理。
?. 操作符表示当对象不为空时正常调用相应的方法,当对象为空时则什么都不做。比如:

if (a != null) {
    a.doSomething()
}
//这段代码使用?.操作符就可以简化成:
a?.doSomething()

?: 操作符

?:操作符表示如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。比如:

val c = if (a ! = null) {
    a
} else {
    b
}
//在Java中我们也可以用三元运算符写成这个样子:
// c = a != null ? a : b
//这段代码的逻辑使用?:操作符就可以简化成:
val c = a ?: b

接下来我们举个🌰来结合使用两个操作符
如果我们有如下业务逻辑:

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

就可以简化为:

fun getTextLength(text: String?) = text?.length ?: 0

此处text可能为null,使用?.操作符,当text为null时,.length代码不执行,因此text?.length返回一个null值,整个表达式就返回0。

!! 非空断言操作符

如果你确定以及肯定你所使用的变量不是null,那么我们可以使用 !! 强行不做空指针的检查

var content: String? = "hello"
fun main() {
  val upCaseStr = content!!.toUpperCase();
}

但是如果这么写,代码好像又不是那么安全了,别急,我们还有办法

3. let函数

let函数是Kotlin中的一个标准函数,了解其它标准函数,请移步:Kotlin-标准函数with,run和apply及静态方法(第一行代码Kotlin学习笔记5)
这个函数提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中,代码如下:
obj.let{objCopy ->
//此处objCopy和obj是同一个对象,我们可以在这里写具体业务逻辑
}
如果我们结合 ?. 操作符,就可以漂亮的解决判空问题了,代码可以这么写:

fun doStudy(study: Study?){
    study?.let{ stu ->
        stu.readBooks()
        stu.doHomework()
    }
}

这里当study对象不为null时,才会执行let函数,然后将study对象传入Lambda表达式中,在Lambda表达式中我们就可以愉快的使用stu对象了。再联系我们上篇学到的知识:->当Lambda表达式的参数列表中只有一个参数时,也不必声明参数名,而是可以使用it关键字来代替,因此我们的代码可以进一步简化:

fun doStudy(study: Study?){
    study?.let{
        it.readBooks()
        it.doHomework()
    }
}

注:let函数可以处理全局变量的判空问题,而if判读语句则无法做到这一点。
在这里插入图片描述

4.延迟初始化lateinit及::xxx.isInitialized

Kotlin的判空机制给我们带来方便的同时,也给我们引出了一些麻烦,比如当我们的页面中有很多个全局变量,并且这些变量在某个生命周期执行时,才能进行初始化,这样,当我们在使用这些变量时,每次都必须进行判空处理,如此又增加了我们代码编写的复杂度。幸运的是,这个问题是有解决办法的,就是使用lateinit关键字对全局变量进行延迟初始化,代码如下:

class MainActivity : AppCompatActivity() {
	//lateinit表示adapter为延迟初始化的变量
    private lateinit var adapter:FruitAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        adapter = FruitAdapter()
        //adapter.xxx这样在使用adapter时,就不需要进行判空处理了
    }
}

需要注意的是如果这么写,就需要你确保在调用相关方法之前,就已经对adapter进行了初始化。另外,我们还可以通过代码判断adapter是否已经进行了初始化,示例如下:

class MainActivity : AppCompatActivity() {
	//lateinit表示adapter为延迟初始化的变量
    private lateinit var adapter:FruitAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //::adapter.isInitialized用于判断adapter是否已经初始化
        if(!::adapter.isInitialized){
			adapter = FruitAdapter()
		}
    }
}

上段代码中,::adapter.isInitialized用于判断adapter是否已经初始化,如果没有,则取!进入if,进行初始化。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值