Kotlin基础学习(三)

191 阅读8分钟

在写这篇文章之前,Kotlin的基础和进阶课程都已经学习完了。这里简单做一个总结:

  • [Kotlin基础学习(一)]主要知识点:Kotlin中的变量与函数,逻辑控制,类与对象

  • [Kotlin基础学习(二)]主要知识点:集合的创建与遍历,Lambda编程

  • [Kotlin基础学习(三)]主要知识点:空指针检查,Kotlin中的简单特性

  • [Kotlin进阶学习(四)] 主要知识点:标准函数,静态方法,变态延迟初始化与密封类

  • [Kotlin进阶学习(五)]主要知识点:扩展函数,运算符重载,高阶函数,内联函数

  • [Kotlin进阶学习(六)]主要知识点:高阶函数的应用,泛型基础,infix函数

  • [Kotlin进阶学习(七)]主要知识点:泛型高级,委托

  • [Kotlin进阶学习(八)]主要知识点:协程的内容

空指针检查

通常情况,我们在安卓开发中总是需要对某个控件或变量进行判空,以确保他不会造成空指针异常。但一旦项目的代码量上去了,总会有出错的时候。Kotlin通过在编译时对判空进行检查,虽然这么搞有时候会让代码很难写,但Kotlin还提供了很多的辅助工具方便处理。

可空类型系统

我们先来看一段代码:

fundoStudy(s:String){
    s.toUpperCase()
    s.toLowerCase()
}

这段看上去似乎有着空指针的风险,但实际上是没有的,因为Koltin默认所有的参数和变量都不可为空。所以这里传入的String参数一定不会为空,调用其中的方法自然没有任何问题。如果你尝试给他传入一个null参数,编译器会报错:

Null can not be a value of a non-null type String

也就是说,Kotlin将空指针异常的检查提前到了编译时期,如果有空指针异常的风险直接不通过编译,更不要谈运行时报错了。这样就从根本上杜绝了空指针异常。

但你看到这里可能会觉得诧异,那我如果真的需要传入一个null值怎么办?Kotlin为我们提供了另外一套可为空的类型系统,即在变量类型后面加上问号即可,如:

fundoStudy(s:String?){
    s.toUpperCase()
    s.toLowerCase()
}

但这时候你会发现出现了红点:

很正常,因为此时我们的s是有可能为空的,必须排除他为空的可能,如下:

fundoStudy(s:String?){
    if(s != null){
		s.toUpperCase() 
         s.toLowerCase()
    }
}

这样就完事了,但我们发现,好像和之前用Java没什么区别啊?我不是还得写一堆的判空语句吗。别急,下面来学习Kotlin提供的辅助工具来更轻松的进行判空。

判空辅助工具

  • 首先来学习最常用的?.操作符,这个符号的意思很好理解——当对象不为空时调用相应的方法,当对象为空的时候就什么都不做,比如我们上面的判空语句:
fundoStudy(s:String?){
    s?.toUpperCase()
    s?.toLowerCase()
}

还是很简单的吧?有了他我们就可以极大的简化我们的代码了。

  • 学习了?.后,再来学习一个也很常用的?:操作符,这个操作符左右都接收一个表达式,如果左边表达式的结果不为空则返回左边表达式的结果,否则就返回右边的结果,比如:
val c = if(a != null){
    a
}else{
    b
}

这段代码的逻辑用?:操作符就可以简化成:

val c = a ?: b

当然,我们也可以将?.和?:一起用起来:

fungetTextLength(text:String?) = text?.length ?: 0

这里使用了函数里单行代码的语法糖,并且结合了?.和?:。具体来看,当text为空时,就会返回0值,当text不为空时,就会返回它的长度length。

  • 当然,有时候Kotlin的编译器并不是那么智能。有的时候我们已经从逻辑上将空指针异常处理了,但Kotlin的编译器不知道,这时候还是会编译失败。比如以下这段代码:
var content:String? = "hello"funmain(){
    if(content != null){
        printUpperCase()
    }
}
funprintUpperCase(){
    val upperCase = content.toUpperCase()
    println(upperCase)
}

这里我们定义了一个可为空的全局变量content,然后在主函数里对其进行了判空处理,当content不为空时才会调用下面的方法输出它的大写值。从逻辑上讲似乎没什么问题,但这段代码无法通过编译。因为printUpperCase()这个方法并不知道外部对content已经进行了判空处理,在调用toUpperCase()方法时还是会认为存在空指针异常。

这种情况下,我们可以使用非空断言工具,写法是在对象的后面加上!!,如:

funprintUpperCase(){
    val upperCase = content!!.toUpperCase()
    println(upperCase)
}

断言,意思就是告诉编译器,我非常确信这里不会为空,你就不用管了。但这种工具如果自己不够确信的情况下,还是不要随意使用的。万一一个不好,就出了空指针异常。

  • 最后,学习一个函数let,通过这个函数的特性可以帮助我们简化空指针检查时的代码。let是一个函数,里面要传入一个Lambda表达式,且会把原始调用对象作为参数传递到Lambda表达式里。那么怎么用他来简化我们的代码呢?如下:
fundoStudy(s:String?){
    s?.toUpperCase()
    s?.toLowerCase()
}

这是我们之前的代码,我们使用let函数:

fundoStudy(s:String?){
    s?.let{s -> 
          s.toUpperCase()
          s.toLowerCase()
    }
}

再根据之前学习的Lambda表达式的特性,代码进一步简化:

fundoStudy(s:String?){
    s?.let{
        it.toUpperCase()
        it.toLowerCase()
    }
}

要解释的话其实很简单,当对象不为空的时调用let函数,此时的s对象一定不为空,自然可以随便调用里面的方法了。

let函数一般用在判断全局变量的时候。在对全局变量判空时,使用if并不能通过编译,因为全局变量的值随时可能被其他线程修改啊,即使做了判空处理也不能确保他没有空指针风险。

Kotlin中的小魔术

到这里,我们已经学习了很多Kotlin的基础知识了。最后,我把书上提到的Kotlin中的小魔术也记录一下,学会了这些也会提升我们的编码速度。

字符串内嵌表达式

一般,我们在Java中连接字符串都会使用+号,但这种方式太繁琐了,一不小心我们就会写错。但Kotlin支持使用字符串内嵌表达式来简化我们的操作。先来看语法规则:

"hello ${obj.name} nice to meet you"

可以看到,Kotlin允许我们在字符串中嵌入${}这种语法结构的表达式,并会自动替换这部分内容。如果学过web的话,会发现el表达式也是这样的写法。另外,当表达式中仅有一个变量时,还可以省略大括号:

"hello $name nice to meet you"

接下来我们直接在代码中使用呗,如下:

val brand = "Samsung"val price = 1299.9
println("Cellphone(brand = $brand, price = $price)")

可以看到,无论是易读性还是易写性都上了一层楼,可以说十分方便了。

函数的参数默认值

如果我们学过C++的话,就会知道C++里是允许在函数上直接给变量一个默认值的。我们的Kotlin也是支持这种写法的。如:

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

这里我们就给str指定了默认值hello,当我们调用这个方法时:

printParams(123)

不传入str的值,输出的结果也会如我们所愿的。

这时候我们换换,给num一个默认值:

funprintParams(num:Int = 100,str:String){
    println("num is $num, str is $str")
}

这时候我们怎么调用这个方法呢?像刚才那样的使用肯定是不行的,编译器会认为我们想把一个字符串赋值给第一个num参数,直接类型不匹配。这时候我们就可以使用键值对的写法了,如下:

printParams(str = "world")

这时候就无所谓先后顺序了,毕竟是按键值匹配的。

这里我们就提到最开始学习的次构造函数,我们说函数的参数默认值这个功能可以很大程度上替代次构造函数。为什么这么说呢?次构造函数的作用一般是什么呢?就是为类提供更多的赋值方式,方便调用。而我们直接在主构造函数上设置函数默认值能达到一样的效果,这时我们就可以使用任意传参组合了。

总结

仔细想想这一路的学习,会发现Kotlin借鉴了很多其他语言的优势,优化了Java的一些痛点。总的来说虽然Lambda表达式的使用和理解还是有些难度,但我想在日后更多的使用中会更加巩固这方面的知识吧。

关注公众号:Android老皮
解锁  《Android十大板块文档》 ,让学习更贴近未来实战。已形成PDF版

内容如下

1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔