Kotlin学习(9)→类基础

瘦欲@ 提交于 2020-01-21 22:25:53


在期待着郭霖先生的《第一行代码(第三版)》时,意识到自己需要补充必要的Kotlin知识,现在通过写博客进行分享,争取拿到书之后早日上手。
使用的软件是IDEA 2019,文章中如有错误或者欠缺的地方,欢迎批评指正。
参考书为《Kotlin从零到精通Android开发》,欧阳燊著,清华大学出版社2018年4月第一版,ISBN 978-7-302-49814-8)。感谢欧阳先生的优秀教材

1 、一个简单的动物类与init函数

根据之前其他语言的基础和Kotlin变量声明规则等,我们可以写一个很简单的动物类:

class Animal {
    var name:String?=null
    var age:String?=null

    init {
        println("Animal: 这是个动物的类")
    }
}

注意,Kotlin要求要么类中的非空成员属性(property)是抽象的(abstract),要么就要初始化。我们将动物的名字(name)和年龄(age)声明为可空类型,初始化为null,没有问题。
下面是一个函数init。从Python的经验看,这是一个“构造函数”,但是与Python中不同的是,这个init函数没有函数声明fun,也没有参数列表,也没有返回值类型。
先来看如何构建这样一个类的对象,我们可以采用如下方法(这和Python是类似的),在main函数中加入:

var animal = Animal()

编译运行,代码通过,控制台输出为Animal:这是个动物的类
于是我们知道,init函数尽管与一般的构造函数不一样,但是它在类对象构造的时候被执行了。
确实如此,init函数不是完整的类的构造函数,而是所谓初始化函数,用于在类对象被创建的时候执行一些操作。

2、类的构造函数

正如上面所说,init函数不是完整的类构造函数,如何让它变完整呢?Java中的构造函数,可以指定参数,然后把参数赋值给对应的类成员,如何为kotlin中的类指定参数,构造出对象呢?

2.1 主构造函数

首要任务是指定构造函数的参数列表。Kotlin中,可以在类名后面加上**constructor(参数列表)**来指定构造函数的参数列表,并且在init函数中,利用这些参数做一些操作。
类名后的constructor(参数列表)与init函数一起,构成了Kotlin类中的主构造函数。
如果参数列表中没有默认参数,则constructor也可以省略,直接写参数列表即可。

修改之前的Animal类代码如下:

class Animal constructor(name:String?,age:Int?){
    var name:String?=null
    var age:Int?=null

    init {
        this.name = name
        this.age=age
    }
}

在上述代码中可以看到,主构造函数中指定了两个参数name和age,然后在init函数中,因为成员变量和参数同名,这时候this关键字起作用了,我们通过this关键字区分成员属性和参数,对成员属性进行了赋值。整个流程和Java是类似的。我们再增添一些内容,完整的测试程序展示如下:

fun main() {
    var rabbit = Animal("兔子",100)
    rabbit.printInfo()
    var cow = Animal("奶牛",1000)
    cow.printInfo()

}
class Animal constructor(name:String?,age:Int?){
    var name:String?=null
    var age:Int?=null

    init {
        this.name = name
        this.age=age
    }

    fun printInfo() {
        print("动物的名字是$name,年龄是$age 岁\n")
    }
}

输出结果是:

动物的名字是兔子,年龄是100 岁
动物的名字是奶牛,年龄是1000 岁

可以得出结论:调用的过程和其他语言是类似的,没有new关键字,和Python相似。这方便了我们的理解(尽管在定义构造函数的时候还要写constructor等等…)

2.2 让类更加符合Kotlin风格

我们虽然解决了构造函数的问题,但是上面的的代码this来this去,因为参数和类属性同名,我们会增加一些编码量,还有一些赋值操作等等。但是将参数名随便给一个a、b显然是不恰当的。Kotlin提供了一种解决办法,使我们可以在声明参数的时候,创建同名的成员属性。,从而一定程度上避免了代码陷入Java风格。
将上面的代码再做修改如下:

fun main() {
    var rabbit = Animal("兔子",100)
    rabbit.printInfo()
    var cow = Animal("奶牛",1000)
    cow.printInfo()

}
class Animal constructor(var name:String?,var age:Int?){
    init {

    }
    fun printInfo() {
        print("动物的名字是$name,年龄是$age 岁\n")
    }
}

注意到一个修改之处是,在参数前面加上了var,这样,就可以在类中创建一个同名的变量属性;如果加上val,则可以创建一个同名常量成员属性。在函数printInfo()中,我们访问了成员属性name和age,证明这两个属性已经存在于类中;并且,init函数内部也没有赋值操作,在构造的时候,这些属性就已经自动赋值了。

这样做,就避免了this的问题,着实是好办法(可以偷懒),但是有时候代码会显得不那么容易阅读(个人感觉)。在类内部加上其他初始化的属性,也是可以的。确实给我们带来了一些方便。

2.3 二级构造函数

我们知道,一般类不止一个构造函数,但是前面介绍的办法,只是创建了一个主构造函数,如果我们需要多种构造方法,就需要借助二级构造函数。
修改上面的动物类代码,通过二级构造函数的办法,给动物以性别:

fun main() {
    var rabbit = Animal("兔子",100)
    rabbit.printInfo()
    var cow = Animal("奶牛",1000,false)
    cow.printInfo()

}
class Animal constructor(var name:String?,var age:Int?){
    var sex:String?="公"
    init {

    }
    constructor(name:String?,age:Int?,sex:Boolean?):this(name,age) {
        this.sex= if(sex==true) "公" else "母"
    }
    fun printInfo() {
        print("动物的名字是$name,年龄是$age 岁,它是$sex$name\n")
    }
}

输出结果:

动物的名字是兔子,年龄是100 岁,它是公兔子
动物的名字是奶牛,年龄是1000 岁,它是母奶牛

大部分代码应该不难理解,这里有几个注意点:

  • 二级构造函数实际上派生于主构造函数(派生后面会讲到),参数列表中必须包含主构造函数的参数列表,给出相应的类型,可以加入新的参数。主构造函数则不用再写类型,只用写参数名即可,如上面的代码所示。
  • 二级构造函数里采用this访问成员属性,这些属性要在类内部声明了。别偷懒了。
  • 可以没有主构造函数,有多个二级构造函数。

如果没有主构造函数,代码就比较贴近Java的风格。不过如果主构造函数设计得好,再写几个二级构造函数,还是相对方便的。

2.4 构造函数的默认参数

Java实际上不支持默认参数,这方面我还在学习,暂时没太弄懂Kotlin的默认参数里手Java支持是怎样做。之后搞懂了会有更新。

3、成员属性和成员方法

Kotlin对象、成员属性、函数等,如果不带修饰符,默认为public,可以通过对象名访问对应的成员属性,调用成员方法。
之前介绍的单例对象比较特殊,它可以直接通过名字来使用方法,相当于Kotlin中的工具类。
对于一个Kotlin类来说,它没有关键字static,不把它写成单例对象的情况下,也是有办法声明静态属性和静态方法的。伴生对象可以做到这一点
关于访问属性限制符,在继承中有更加详细的描述。

4、伴生对象

伴生对象与单例对象类似,同属于对象,但是伴生对象相当于类的孩子,必须写在类中。使用伴生对象的成员(例如成员属性)有两种办法:
一种是类名.伴生对象名.伴生对象成员属性,另一种是类名.伴生对象成员属性,后一种办法与Java使用静态成员属性的形式是一致的。
引入伴生对象成员属性的想法和Java是类似的,例如0和1,对于其他人来说根本不知道表示的是什么意思,但是对于MALE和FEMALE,我们知道它们表示男、女。(原谅英语词汇比较贫乏,这里也表示公母)。将之前的Animal类代码再做修改:

fun main() {
    var rabbit = Animal("兔子",100)
    rabbit.printInfo()
    var cow = Animal("奶牛",1000,Animal.FEMALE)
    cow.printInfo()

}
class Animal (var name:String?,var age:Int?){
    var sex:String?="公"
    init {
    }
    constructor(name:String?,age:Int?,sex:Boolean?):this(name,age) {
        this.sex= if(sex==true) "公" else "母"
    }
    fun printInfo() {
        print("动物的名字是$name,年龄是$age 岁,它是$sex$name\n")
    }
    companion object WildAnimal {
        val MALE:Boolean=true
        val FEMALE:Boolean =false
    }
}

伴生对象的声明关键字是companion object,需要写在类中,并且名字不能和类名一致。在这里,声明了两个常量MALE和FEMALE,再将main函数中的调用的参数改为FEMALE,这样想表达的意思就会清晰很多。
伴生对象中的函数也是类似的。

5、总结

(来自欧阳先生的教材上的总结)
Kotlin的类成员分为实例成员静态成员,其中,实例成员直接在类中声明,包括实例属性和实例方法,与主构造函数参数相同的实例成员属性可以在构造函数中直接声明。需要通过对象才能访问实例成员。
静态成员在伴生对象中定义,可以通过类名直接访问。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!