在期待着郭霖先生的《第一行代码(第三版)》时,意识到自己需要补充必要的Kotlin知识。现在写文章分享自己学习Kotlin基础知识的过程,争取拿到书之后能早日上手。
使用的软件是IDEA 2019,文章中如有错误或者欠缺的地方,欢迎批评指正。
(参考书为《Kotlin从零到精通Android开发》,欧阳燊著,清华大学出版社2018年4月第一版,ISBN 978-7-302-49814-8)。感谢欧阳先生的优秀教材。
Kotlin类的知识快整理完了,最后还要讲几个特殊的Kotlin类,这些特殊的类为开发者提供了极大地方便。
1、嵌套类
Kotlin的类中允许再定义一个类,按照普通的类声明方法即可,这样就能声明出一个嵌套类。
例如(代码来源于参考书):
class Tree(var treename:String) {
class Flower(var flowerName:String) {
fun getName():String {
return "这是一朵$flowerName"
}
}
}
如上所示,Flower就是Tree的嵌套类,注意嵌套类不能访问外部的类的成员。
要使用嵌套类,需要通过外部的类的类名调用。如下:
class Tree(var treename:String) {
class Flower(var flowerName:String) {
fun getName():String {
return "这是一朵$flowerName"
}
}
}
fun main() {
var result = Tree.Flower("小花").getName()
println(result)
}
输出结果:
这是一朵小花
2、内部类
内部类可以访问外部类的成员,没有了之前嵌套类的局限性。在class前面加入inner即可表示内部类声明,我们在之前的代码基础上修改:
class Tree(var treename:String) {
inner class Flower(var flowerName:String) {
fun getName():String {
//可以访问外部类成员treename
return "这是${treename}上的一朵$flowerName"
}
}
}
fun main() {
var result = Tree("桃树").Flower("小花").getName()
println(result)
}
输出结果:
这是桃树上的一朵小花
可以看到,内部类是需要通过外部类对象来调用的。
3、枚举类
Java和C++都有枚举类型。这些枚举类型可以有效地表达语义。之前学习过的伴生对象,其中定义的属性,也能够当做静态属性使用,同样可以起到明确语义的效果,一般是声明一些常量。
声明一个枚举类非常简单,我们准备好一个类,用enum关键字声明其为枚举类:
enum class Chicken(val chickenName:String) {
COCK("公鸡"),
HEN("母鸡"),
CHICK("小鸡")
}
枚举类内部可以直接写枚举变量,如上面的COCK、HEN、CHICK。可以为Chicken枚举类指定参数列表,实际上,枚举变量是枚举类的实例,因此,每个枚举变量可以调用构造函数进行成员属性的赋值。如COCK对应的公鸡、HEN对应母鸡,CHICK对应小鸡。
枚举变量间要用逗号分开,即便是不在同一行也是如此。
枚举变量自身已经有一些属性,如ordinal属性用于获取该枚举值的序号,name属性用于获取枚举值的名称。
我们再修改一下代码,补上主函数,查看枚举变量的属性:
enum class Chicken(val chickenName:String) {
COCK("公鸡"),
HEN("母鸡"),
CHICK("小鸡")
}
fun main() {
var chickArray:Array<Chicken> = arrayOf(Chicken.COCK,Chicken.HEN,Chicken.CHICK)
var i=2
while(i>=0) {
println("${chickArray[i].ordinal}号是${chickArray[i].name}")
i--
}
}
可见,可以通过枚举类名访问枚举类中的枚举变量。
输出的结果是:
2号是CHICK
1号是HEN
0号是COCK
由此应该清楚了ordinal和name指的是什么了。
4、密封类
密封类的提出可以解决枚举类的多余分支问题。例如用when-else选择枚举类,本来枚举类有四个值,照理说四个条件和选择即可,但是最后一定要有一个else分支,这是多此一举的。但这样做是因为,when不知道枚举类型中有4个枚举值,为了以防万一,必须要有else分支,用来输出一些信息等。
Kotlin提出了密封类的概念,就像是一种更加严格的枚举类。密封类要加关键字sealed声明,内部采用嵌套类的办法,内部的嵌套类全部由自身派生而来。
先给出一个形式上的密封类定义:
sealed class Season {
class Spring(var name:String) : Season()
class Summer(var name:String) : Season()
class Autumn(var name:String) : Season()
class Winter(var name:String) : Season()
}
四个季节的类,都被装在密封类Season中。这样的话,我们可以不用指定else,输出各个信息:
sealed class Season {
class Spring(var name:String) : Season()
class Summer(var name:String) : Season()
class Autumn(var name:String) : Season()
class Winter(var name:String) : Season()
}
fun main() {
var choose:Season = Season.Spring("春天")
when(choose) {
is Season.Spring->println("是的")
is Season.Summer->println("不是夏天")
is Season.Autumn-> println("不是秋天")
is Season.Winter-> println("不是冬天")
}
}
这是一个很简单的程序,输出的结果是:
是的
其实我觉得密封类还有很多好处,比如可以更加明确的指定类之间的关系,比起让四季的类继承同一个季节类,我认为有时候把它们密封到一个季节类中会更好。
之后还有数据类和模板类,稍后更新。
5、数据类
程序开发免不了要用到各种数据,一般的,我们会建立一个实体类来存储这些数据,配套会有对数据进行各种操作的函数,如增、删、查、改等等的工具函数。这样的思路是很正常的,但是会增加很多编码量。
Kotlin提出了数据类的概念,一定程度上解放了程序员。在class前加上data,即可将该类声明为数据类。
当然,数据类也有一些要求:
- 数据类必须要有主构造函数,且至少要有一个输入参数,因为它的属性要和输入参数一一对应,没有属性这个类也就存不了数据了;
- 主构造函数的参数名前要加val或者var,原因上一点已经说明了
- 数据类是一个单独的类,有自己的规则,不能是其他类,如子类、基类、密封类、内部类、嵌套类等。
数据类的方便之处在于:
- 自动声明与构造函数参数同名的属性(因为要带val或者var)
- 自动实现每个属性的get、set方法
- 自动提供equal方法,判断两个属性是否相等
- 自动提供copy方法,用于复制数据,也可以给指定的参数另外复制
- 提供toString方法,用于打印数据中保存的所有字段值。
下面看代码,准备好一个数据类Animal,写代码可能更加简单易懂。
data class Animal(val Pig:String,val Chick:String,val Dog:String,val Horse:String,val Fox:String) {
//准备好数据类,创建好五个属性,Pig、Chick、Dog、Horse和Fox,都为String类型
}
fun main() {
var farm = Animal("小猪","小鸡","小狗","小马","狐狸")
//复制Farm到变量Zoo,动物都进动物园
var zoo = farm.copy()
//猪出栏了,现在新建newZoo,没有猪
var newZoo = farm.copy(Pig="没有小猪了")
if(newZoo.equals(zoo)) println("新动物园和老动物园一样") else println("新动物园和老动物园不一样")
var whatIsInZoo = newZoo.toString()
println(whatIsInZoo)
}
输出结果:
新动物园和老动物园不一样
Animal(Pig=没有小猪了, Chick=小鸡, Dog=小狗, Horse=小马, Fox=狐狸)
6、模板类
模板类的思路和泛型函数是差不多的,在类名后面加上尖括号T即可将该类声明为模板类, 一般类中有些属性中用到了这个不确定的类型T,我们就把它声明为模板类。
下面给出一个很简单的模板类,接给出使用模板类创建对象的办法:
class TemplateTest<T>(var message:T?=null) {
fun printMessage() {
println(message)
}
}
fun main() {
var test = TemplateTest<String>("SSSS.GRIDMAN")
test.printMessage()
var test2 = TemplateTest<Int>(2333)
test2.printMessage()
}
输出结果:
SSSS.GRIDMAN
2333
在声明数组的时候,我们就经常用到模板类的办法,可以回想一下。模板类用得好应该还是非常方便的。。。
至此欧阳先生书上关于Kotlin基础语法的部分就介绍到这里了,实际上Kotlin中还有异常捕获和处理等知识,之后依然会有更新。
再次感谢欧阳先生的精彩教材。用来学习基础语法真的挺不错的。不过,教材中关于Android开发的部分,还是要先有一定的开发基础才能理解的比较好,建议先学一学郭霖先生的教材…
关于基础知识总结完之后,我会进行关于Kotlin开发Android应用程序的各种知识的更新,如果喜欢请继续关注。谢谢。