开发区网站建设/廊坊百度关键词排名平台
1. 空指针检查
这里先定义一个Study接口:
//定义Study接口
interface Study {
//readBooks()方法fun readBooks()//doHomeWork()方法fun doHomeWork(){println("I'm doing homework now.")}
}
这里定义了学生类来实现接口:
//主函数
fun main() {
}
//Person类 有name和age字段
open class Person(val name: String, val age: Int) {}
//学生类 继承了Person类 实现了Study接口
class Student(name: String, age: Int) : Person(name, age), Study {override fun readBooks() {println("$name is reading")}}
//定义了一个实现 Study接口中的方法的方法
fun doStudy(study: Study) {study.readBooks()study.doHomeWork()
}
我们来看这个方法:
fun doStudy(study: Study) {study.readBooks()study.doHomeWork()
}
这个方法一定安全吗,如果我们向doStudy()传入了一个null参数,
那么这里毫无疑问会发生空指针异常,因此,更加稳妥的方法是:
fun doStudy(study: Study) {if (study != null) {study.readBooks()study.doHomeWork()}
}
这样就可以保证不管传入的参数是什么,这段代码始终都是安全的.
可见,这么一小段代码都有可能发生空指针异常,在一个大型项目中,
想要完全避免非空指针几乎是不可能的事情,这也是它高居各类崩溃排行榜首位的原因.
Kotlin给我们提供了一套可为空的类型:在类名的后面加上一个问号就行了.
如Int表示不可为空的整型,而Int?表示可为空的整型.
现在代码就可以正常编译通过了,并且完全不会出现空指针异常.
2. 判空辅助工具
2.1 ?.操作符:
当对象不为空时正常调用相应的方法,当对象为空时则什么都不去做
比如下面代码:
fun main() {val student = Student("Tom", 18)if (student != null) {student.doSomething()}
}
可以简化为:
val student = Student("Tom", 18)student?.doSomething()
可以看见我们借助?.操作符就省去了if判断语句,
你现在可能觉得没什么,但当以后进入大型项目的时候,这点就非常有用了.
2.2 ?:操作符
这个操作符左右两边都接收一个表达式
如果左边表达式不为空就返回左边表达式的结果,
否则就返回右边表达式的结果.例如:
var c = if (a != null) {a} else {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可能是空的,因此我们在调用它的length字段时需要使用
?.操作符,而当text为空时 text?.length会返回一个null值,
这个时候我们再去借助?:操作符使它返回0.
不管Kotlin的空指针检查机制也不是完美的,
有的时候我们逻辑上面已经将空指针异常处理了,
但是Kotlin编译器并不知道,这个时候它还是会编译失败.
因为printUpperCase()函数并不知道外部已经对content变量进行了非空检查,
在调用printUpperCase()方法室,还认为这里存在空指针异常,所有无法通过.
在这种情况下,我们可以使用非空断言工具,写法是在对象的后面加上!!
fun printUpperCase() {val upperCase = content!!.toUpperCase()println(upperCase)
}
这是一种有风险的写法,表明自己非常确信这里的对象不为空.
2.3 let辅助工具
最后我们来学习let辅助工具:
let并不是关键字,也不是操作符,而是一个函数.
这个函数提供了函数式API的编程接口,
并且将原始调用对象作为参数传递到Lambda表达式中.
示例代码:
obj.let{obj2 -> //编写具体的业务逻辑
}
这里调用了obj对象的let函数, 然后再Lambda表达式中的代码就会立即执行,
并且这个obj对象还会作为参数传入到Lambda表达式,不过为了防止变量重名,
这里我将参数名改为了obj2,但实际上他们是同一个对象,
这就是let函数的作用.我们回到doStudy()函数里:
fun doStudy(study: Study?) {study?.readBooks()study?.doHomeWork()
}
虽然可以编译通过,但这种方式有点啰嗦,这段代码如果使用if语句就会变成:
fun doStudy(study: Study?) {if (study != null) {study.readBooks()}if (study != null) {study.doHomeWork()}
}
这就是说,我们每进行一次if判断就可以随意调用study对象的任何方法
就可以随意调用study对象的任何方法,
但受限制与?.操作符, 现在每调用一次study对象的方法都要进行一次if判断.
接下来我们使用?.操作符结合let函数来对代码进行优化:
fun doStudy(study: Study?) {study?.let { study ->study.readBooks()study.doHomeWork()}
}
?.操作符表示对象为空时什么也不做,
对象不为空时就调用let函数,而let函数会将study对象本身作为参数传递到
Lambda表达式中,此时对象肯定不为空,我们就可以放心得调用它的任意方法了.Lambda表达式中,如果参数只有一个,则可以使用it关键字替代,所以代码可以简化为:
fun doStudy(study: Study?) {study?.let {it.readBooks()it.doHomeWork()}
}
let函数是可以处理全局变量的判空问题的,而if条件语句则无法做到这一点:
使用let不报错:
使用if报错:
之所以会报错,是因为全局变量随时都有可能被其他线程修改,即使做了非空判断,
也无法保证if语句中的study没有空指针风险.从这一点也可以看出let函数的优势.