Kotlin安卓开发学习(2)

和很多现代高级语言一样,Kotlin也是面向对象的。

类与对象

Kotlin的类基本语法为:

class Person {
    var name = ""
    var age = 0
    fun eat() {
       println(name + " is eating. He is " + age + " years old.")
    }
}

这样就可以对类进行实例化并调用:

val p = Person()
val age=p.age

这里还有一个语法糖,在使用Kotlin时,我们不需要像Java那样定义 get/set 方法,在调用对象的属性时也不需要调用get/set方法来使用属性。使用Kotlin时,我们是直接对属性进行赋值和读取,因为Kotlin会自动将赋值、读取语句转换成get/set方法,方便开发者使用。

继承与构造函数

Kotlin进行类的继承流程与Java有着不小的差距。首先,想要一个类被继承,首先需要这个类可以被继承。这就是Kotlin不同的地方,在Kotlin中任何一个非抽象类默认都是不可以被继承的,相当于Java中给类声明了final关键字。因此,如果我们需要一个类可以被继承,需要在类前添加 修饰符”open“:

open class Person {
   …
}

加上open关键字之后,我们就是在主动告诉Kotlin编译器,Person这个类是专门为继承而设计的,这样Person类就允许被继承了。

之后,我们需要让子类去继承父类。

在Java中继承的关键字是extends,而在Kotlin中变成了一个冒号,写法如下:

class Student : Person() {
    var sno = ""
    var grade = 0
}

与Java中不同,Kotlin中继承需要在继承类后面加上一对括号,为什么需要加上括号,这就涉及了Kotlin主构造函数和次构造函数的概念。

主构造函数是我们最常用的构造函数,每个类默认都会有一个不带参数的主构造函数,当然也可以显式地给它指明参数。主构造函数的特点是没有函数体,直接定义在类名的后面即可。比如下面这种写法:

class Student(val sno: String, val grade: Int) : Person() {
}

这里我们将学号和年级这两个字段都放到了主构造函数当中,这就表明在对Student类进行实例化的时候,必须传入构造函数中要求的所有参数。比如:

val student = Student("a123", 5)

这样我们就创建了一个Student的对象,同时指定该学生的学号是a123,年级是5。另外,由于构造函数中的参数是在创建实例的时候传入的,不像之前的写法那样还得重新赋值,因此我们可以将参数全部声明成val。

当然,主构造函数没有函数体,如果我们需要在主构造函数里编写一些逻辑,可以使用init结构体,所有主构造函数中的逻辑都可以写在里面:

class Student(val sno: String, val grade: Int) : Person() {
    init {
        println("sno is " + sno)
        println("grade is " + grade)
    }
}

这里,我们在创建实例之后一定会创建一个带有初始值的对象,且打印这些值。

但是这和那对括号又有什么关系呢?这就涉及了Java继承特性中的一个规定,子类中的构造函数必须调用父类中的构造函数,这个规定在Kotlin中也要遵守。

根据继承特性的规定,子类的构造函数必须调用父类的构造函数,可是主构造函数并没有函数体。Kotlin当然没有采用这种设计,而是用了另外一种简单但是可能不太好理解的设计方式:括号。子类的主构造函数调用父类中的哪个构造函数,在继承的时候通过括号来指定。

因此,如果我们在父类主构造函数上定义参数,在继承时也要在括号里定义。

class Student(val sno: String, val grade: Int, name: String, age: Int) :
  Person(name, age) {
     …
}

之后我们就可以来创建实例:

val student = Student("a123", 5, "Jack", 19)

之后,我们需要了解次构造函数,任何一个类只能有一个主构造函数,但是可以有多个次构造函数。次构造函数也可以用于实例化一个类,这一点和主构造函数没有什么不同,只不过它是有函数体的。

Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)。这里我通过一个具体的例子就能简单阐明,代码如下:

class Student(val sno: String, val grade: Int, name: String, age: Int) :
   Person(name, age) {
    constructor(name: String, age: Int) : this("", 0, name, age) {
    }
    constructor() : this("", 0) {
    }
}

次构造函数是通过constructor关键字来定义的,这里我们定义了两个次构造函数:第一个次构造函数接收name和age参数,然后它又通过this关键字调用了主构造函数,并将sno和grade这两个参数赋值成初始值;第二个次构造函数不接收任何参数,它通过this关键字调用了我们刚才定义的第一个次构造函数,并将name和age参数也赋值成初始值,由于第二个次构造函数间接调用了主构造函数,因此这仍然是合法的。

这样,我们就拥有了三种方法来实例化类:

val student1 = Student()
val student2 = Student("Jack", 19)
val student3 = Student("a123", 5, "Jack", 19)

接下来我们就再来看一种非常特殊的情况:类中只有次构造函数,没有主构造函数。这种情况真的十分少见,但在Kotlin中是允许的。当一个类没有显式地定义主构造函数且定义了次构造函数时,它就是没有主构造函数的。我们结合代码来看一下:

class Student : Person {
    constructor(name: String, age: Int) : super(name, age) {
    }
}

这里因为Student后面没有显示地定义主构造函数,同时又因为定义了次构造函数,所以现在Student类是没有主构造函数的。因为没有,所以继承Person时不需要加上括号。

另外,由于没有主构造函数,次构造函数只能直接调用父类的构造函数,上述代码也是将this关键字换成了super关键字,这部分就和Java很像了。

接口

Kotlin的接口和Java的很像,使用interface修饰,函数不要求有函数体:

interface Study {
    fun readBooks()
    fun doHomework()
}

这样就可以去实现接口了:

class Student(name: String, age: Int) : Person(name, age), Study {
    override fun readBooks() {
        println(name + " is reading.")
    }
    override fun doHomework() {
        println(name + " is doing homework.")
    }
}

在Java中,我们使用extends进行继承,使用implements进行接口使用,但在Kotlin中统一使用冒号,中间用逗号进行分隔。且因为接口没有构造函数,所以不需要写上括号。

因为接口中定义了待实现函数,所以我们使用接口后必须对函数进行实现。Kotlin中,我们使用override关键词来重写或者实现接口中的函数。

除此之外Kotlin还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。代码如下:

interface Study {
     fun readBooks()
     fun doHomework() {
        println("do homework default implementation.")
     }
}

这里我们在 doHomework方法上加了函数体。如果接口中一个函数拥有了函数体,这个函数体的内容就是它的默认实现。现在当一个类去实现这个接口时,只会强制要求实现readBooks()函数,而doHomework()函数则可以自由选择实现或者不实现,不实现时就会自动使用默认的实现逻辑。

Java和Kotlin函数可见性修饰符对照表

修饰符JavaKotlin
public所有类可见所有类可见(默认)
private当前类可见当前类可见
protected当前类、子类、同一包路径下的类可见当前类、子类可见
default同一包路径下的类可见(默认)
internal同一模块中的类可见