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,进行初始化。