这里先定义一个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?表示可为空的整型.
现在代码就可以正常编译通过了,并且完全不会出现空指针异常.
?.操作符:当对象不为空时正常调用相应的方法,当对象为空时则什么都不去做
比如下面代码:
fun main() {
val student = Student("Tom", 18)
if (student != null) {
student.doSomething()
}
}
可以简化为:
val student = Student("Tom", 18)
student?.doSomething()
可以看见我们借助?.操作符就省去了if判断语句,你现在可能觉得没什么,但当以后进入大型项目的时候,这点就非常有用了.
?:操作符:这个操作符左右两边都接收一个表达式,如果左边表达式不为空就返回左边表达式的结果,否则就返回右边表达式的结果.例如:
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)
}
这是一种有风险的写法,表明自己非常确信这里的对象不为空.
最后我们来学习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函数的优势.
谢谢观看,一起进步鸭