跟郭神学Kotlin(第三行学习笔记04)

空指针

Android上奔溃率最高的异常类型就是 空指针异常 —— NullPointerException

可空类型系统

Kotlin 利用编译时判空检查的机制几乎杜绝了空指针异常。

Kotlin 提供了一系列辅助工具,让我们能轻松地处理各种判空情况。

拿之前的代码举例了一下:

fun doStudy(study: Study) {
    study.doHomework()
    study.readBook()
}

如果直接运行该代码:

fun testNullPointer(){
    //直接传入空值
    doStudy(null)
              ↑
| Null can not be a value of a non-null type Study |
    
}

可以看到,当我们传入 null 时编译器已经报异常了!

因为Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数一定不为空。

由于Kotlin将空指针异常的检查提前到了编译时期,像这样直接传null就会报错,当我们修正后才能正常运行。

那么有的同学可能会问,我就TM要传 null 值 该怎么做?

Kotlin为我们提供了一套 [ 可为空的类型系统 ] 只不过在使用 [ 可为空的类型系统 ] 时,我们需要在编译时期就将所有潜在的空指针异常都处理掉,否则代码将无法编译通过。

什么是 [ 可为空的类型系统 ] ?

就是在类名的后面加上一个问号。

比如:

    //表示不可为空的整型
    Int

    //表示可为空的整型
    Int?
    
    //表示不可为空的字符串
    String
    
    //表示可为空的字符串
    String?
    

再回到上面的 doStudy()

我们将在Study后面加个

fun doStudy(study: Study?) {
    study.doHomework()
    study.readBook()
}

可以看到 调用该方法时不会报错了

fun testNullPointer(){
    //直接传入空值
    doStudy(null)
}

不过啊,doStudy里面又报错了。因为study可能是空的啊,调用doHomework()就可能导致异常。

我们还需要 处理一下里面

比如:

fun doStudy(study: Study?) {
    
    if (study!=null){

        study.doHomework()
        study.readBook()
        
    }
    
}

可以看到,我们加了个判断,如果为 null 就不执行了。这样也就不会报异常了。

不过这样写看起来很麻烦,如果有大量或者全局的判空 这样用起来就会很捉急。

接下来 将展示 Kotlin 提供的一系列辅助工具 这些判空将会很简单。

判空辅助工具

  • ?. 操作符(注意: 问号后面还有个

当对象不为空时正常调用,当对象为空时就什么也不做。

上面的study就可以简化为:

    //原始
    if (study!=null){

        study.doHomework()
        study.readBook()

    }

    //简化后
    study?.doHomework()
    study?.readBook()

感觉是真TM的方便!

  • ?: 操作符(注意: 问号后面还有个冒号

操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空就返回左边表达式的结果,否则就返回右边表达式的结果。

听着描述有点像三目运算符的感觉不。

我这里还是用study举例吧,好理解:

fun doStudy3(study: Study?) {


    val a = study
    val b = "something"
    
    //原始
    val c = if (a !=null){
        a
    }else{
        b
    }
    
    //简化后
    val c = a ?: b

}

解释下:当 a 不为空时 c 就等于 a ,若 a 为空时 那么 c 就等于 b 。

  • ?. 和 ?: 结合在一起举例

//传统写法
fun getTextLength(text: String?): Int {

    if (text != null) {
        return text.length
    }
    
    return 0

}

//简化后
fun getTextLen(text: String?): Int {
    
    return text?.length ?: 0
    
}

//再简
fun getTL(text: String?) = text?.length ?: 0

这波啊,这波是我没学熟练,所以不能一次变到位。
这里:首先由于text是可能为空的,因此我们在调用它的length字段时需要使用 ?. 操作符,而当text为空时,text?.length会返回一个null值,这时我们再借助 ?: 操作符让它返回0。

  • !! 非空断言(两个感叹号)

虽然Kotlin写起来很方便,不过有时候还是显得不那么聪明。

有种情况,虽然我们已经将空指针异常处理了,但是Kotlin编译器还是不让我们通过。

举例:

var content: String? = "hello"

fun runUnAI() {

    if (content != null) {
        UpCase()
    }

}

fun UpCase() {
    val upContent = content.toUpperCase()
    print("大写后:$upContent")

}

编译器已经报错了,并且是运行不起来的。

可以看到,在 runUnAI()方法 中 我们已经做了content的判空,但是在UpCase()中,它并不知道。所以在UpCase里调用toUpperCase编译器还是会认为这是有风险的,从而不让编译通过。

那应该怎么办呢?非空断言工具,在对象后面直接加 !!

fun UpCase() {
    val upContent = content!!.toUpperCase()
    print("大写后:$upContent")

}

可以看到,编译器不再报错了。

这就是等于告诉 Kotlin编译器,我已经做了处理了,你不用管了。如果出错,直接抛异常即可。

我的代码,我嗦了蒜!

  • let 函数

它提供了函数式API的编程接口,并将原始调用对象作为参数传递到Lambda表达式中

fun letTest() {

    val obj = 3

    obj.let {
    
        //你的逻辑
        doStudy(Student("",0))

        println("fuck this is let")

        println("let:${it + 2}")

    }

}

可以看到,这里调用了obj对象的let函数,然后Lambda表达式中的代码就会立即执行,并且这个obj对象本身还会作为参数传递到Lambda表达式中。

这里实际写法 和 郭神 讲的有点不太一样。

不过这都不是重点,重点是:

let函数 属于 Kotlin中的 标准函数,这个let函数的特性配合 ?. 操作符使用:

回到doStudy

fun doStudy2(study: Study?) {

    study?.doHomework()
    study?.readBook()

}

这种写法虽然有一定的简化,但其实这种表达方式有点啰嗦,这种写法本质是:

fun doStudy2(study: Study?) {

    if (study != null) {
        study.doHomework()
    }
    
    if (study != null) {
        study.readBook()
    }
    
}

可以看到 等于 调用了两次判空。

如果我们用 let?. 结合起来

fun doStudyPlus(study: Study?) {

    study?.let {

        it.readBook()
        it.doHomework()

    }

}

就变成了这样: ?.操作符 表示对象不为空调用 let函数,而 let函数 会将 Study对象本身作为参数传递到Lambda表达式中,此时Study肯定不为空,就可以随便调用了。

  • let对全局变量的处理

let函数 是可以处理 全局变量 的判空问题的,而if判断语句则无法做到这一点。

如果我们将doStudy()函数中的参数变成一个全局变量,使用let函数任然正常工作,但使用if判断语句 就会报错。

var study:Study? =null//注意这里是变量

fun doStudyPlusLet() {

    study?.let {
        
        //这里正常运行
        it.readBook()
        it.doHomework()

    }

}

fun doStudyPlusIf() {

    if (study!=null){
    
        //这里会报错
        study.readBook()
        study.doHomework()

    }

}

为什么呢?这是因为:

全局变量的值随时都有可能被其他线程所修改,即使做了判空处理,仍然无法保证if语句中的Study变量没有空指针风险。

Kotlin中的小魔术(小技巧)

字符串内嵌表达式

在Kotlin里,我们不需要像Java里拼接字符串了,而是可以直接将表达式写在字符串里:

fun magicTest() {

    val student = Student("WaHaa", 10)

    println("fuck u , my dear ${student.name} !")

}

可以看到,Kotlin允许我们在字符串里嵌入 ${} 这种语法结构的表达式,并在运行时使用表达式执行的结果替代这部分内容。

另外,当表达式中仅有一个变量时,还可以将两边的大括号省略:

fun magicTest0() {

    val str = "Don't take yourself humiliation"

    println("fuck u , $str !")

}

函数的参数默认值

在前边我们学 次构造函数时说过,次构造函数 在Kotlin中很少用。因为Kotlin提供了函数设定参数默认值的功能,他在很大程度上能替代次构造函数的作用。

我们可以再定义函数的时候给任意参数设定一个默认值,这样当调用此函数时就不会强制要求 调用方 为此函数传值,在没有传值的情况下会自动使用参数的默认值。

fun printParams(num: Int, str: String = "hello") {
    println("num is $num , str is $str")
}

可以看到,这里我们给printParams()函数的第二个参数设定了一个默认值,这样当调用printParams()函数时,可以选择给第二个参数传值,也可以选择不传,在不传的情况下就会自动使用默认值

fun printParTest() {

    printParams(100, "ddd")

    printParams(101)

}

可以看到,在没有给第二个参数传值的情况下,printParams()函数 自动使用了参数的默认值。

郭神又将骚东西了,将代码的第一个参数设置默认值

fun printParams1(num: Int = 102, str: String) {
    println("num is $num , str is $str")
}

这时如果想让num参数使用默认值该怎么办呢?模仿刚才的写法肯定是行不通的,因为编译器会认为我们想把字符串赋值给第一个 num 参数,从而报类型不匹配的错误:

fun printParTest1() {

    printParams1(100, "ddd")

    printParams1("ddd")

}

不过 不必担心,Kotlin提供了另外一套机制

通过键值对的方式传值

稍加改造

fun printParTest1() {

    printParams1(102 ,"ddd")

    printParams1(num = 103,str = "ddd")
    
    printParams1(str = "ddd")

}

可以看到,此时编译器不再报错。并且运行正常。

所以说 这种写法 可以在很大程度上替代次构造函数的作用。只需给个默认值即可。

前边学的Student就可以改造成

//之前
class Student(val sno: String, val grade: Int, name: String, age: Int) : Person(name, age) {

    constructor(name: String, age: Int) : this("", 0, "", 0) {
    }

    constructor() : this("dd", 0) {
    }

}

//改造后
class Students(val sno:String = "", val grade:Int = 0,name:String = "",age:Int=0):Person(name,age){

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值